/* * 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; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.Display; import android.view.IWindowManager; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewParent; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; import android.widget.DateTimeView; import android.widget.ImageView; import android.widget.RemoteViews; import android.widget.TextView; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; import com.android.internal.util.NotificationColorUtil; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SwipeHelper; import com.android.systemui.SystemUI; import com.android.systemui.assist.AssistManager; import com.android.systemui.recents.Recents; import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.phone.NavigationBarView; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.PreviewInflater; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; import java.util.List; import java.util.Locale; import static com.android.keyguard.KeyguardHostView.OnDismissAction; public abstract class BaseStatusBar extends SystemUI implements CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener, RecentsComponent.Callbacks, ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment { public static final String TAG = "StatusBar"; public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final boolean MULTIUSER_DEBUG = false; // STOPSHIP disable once we resolve b/18102199 private static final boolean NOTIFICATION_CLICK_DEBUG = true; public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.child_notifs", false); protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; protected static final int MSG_PRELOAD_RECENT_APPS = 1022; protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024; protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025; protected static final boolean ENABLE_HEADS_UP = true; // scores above this threshold should be displayed in heads up mode. protected static final int INTERRUPTION_THRESHOLD = 10; protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; // Should match the value in PhoneWindowManager public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; private static final String BANNER_ACTION_CANCEL = "com.android.systemui.statusbar.banner_action_cancel"; private static final String BANNER_ACTION_SETUP = "com.android.systemui.statusbar.banner_action_setup"; protected CommandQueue mCommandQueue; protected IStatusBarService mBarService; protected H mHandler = createHandler(); // all notifications protected NotificationData mNotificationData; protected NotificationStackScrollLayout mStackScroller; protected NotificationGroupManager mGroupManager = new NotificationGroupManager(); // for heads up notifications protected HeadsUpManager mHeadsUpManager; protected int mCurrentUserId = 0; final protected SparseArray mCurrentProfiles = new SparseArray(); protected int mLayoutDirection = -1; // invalid protected AccessibilityManager mAccessibilityManager; // on-screen navigation buttons protected NavigationBarView mNavigationBarView = null; protected Boolean mScreenOn; // The second field is a bit different from the first one because it only listens to screen on/ // screen of events from Keyguard. We need this so we don't have a race condition with the // broadcast. In the future, we should remove the first field altogether and rename the second // field. protected boolean mScreenOnFromKeyguard; protected boolean mVisible; // mScreenOnFromKeyguard && mVisible. private boolean mVisibleToUser; private Locale mLocale; private float mFontScale; protected boolean mUseHeadsUp = false; protected boolean mHeadsUpTicker = false; protected boolean mDisableNotificationAlerts = false; protected DevicePolicyManager mDevicePolicyManager; protected IDreamManager mDreamManager; PowerManager mPowerManager; protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; protected int mRowMinHeight; protected int mRowMaxHeight; // public mode, private notifications, etc private boolean mLockscreenPublicMode = false; private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); private NotificationColorUtil mNotificationColorUtil; private UserManager mUserManager; // UI-specific methods /** * Create all windows necessary for the status bar (including navigation, overlay panels, etc) * and add them to the window manager. */ protected abstract void createAndAddWindows(); protected WindowManager mWindowManager; protected IWindowManager mWindowManagerService; protected abstract void refreshLayout(int layoutDirection); protected Display mDisplay; private boolean mDeviceProvisioned = false; private RecentsComponent mRecents; protected int mZenMode; // which notification is currently being longpress-examined by the user private NotificationGuts mNotificationGutsExposed; private TimeInterpolator mLinearOutSlowIn, mFastOutLinearIn; /** * The {@link StatusBarState} of the status bar. */ protected int mState; protected boolean mBouncerShowing; protected boolean mShowLockscreenNotifications; protected NotificationOverflowContainer mKeyguardIconOverflowContainer; protected DismissView mDismissView; protected EmptyShadeView mEmptyShadeView; private NotificationClicker mNotificationClicker = new NotificationClicker(); protected AssistManager mAssistManager; @Override // NotificationData.Environment public boolean isDeviceProvisioned() { return mDeviceProvisioned; } protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { final boolean provisioned = 0 != Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); if (provisioned != mDeviceProvisioned) { mDeviceProvisioned = provisioned; updateNotifications(); } final int mode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); setZenMode(mode); updateLockscreenNotificationSetting(); } }; private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, // so we just dump our cache ... mUsersAllowingPrivateNotifications.clear(); // ... and refresh all the notifications updateNotifications(); } }; private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { @Override public boolean onClickHandler( final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { if (DEBUG) { Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); } logActionClick(view); // 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. try { ActivityManagerNative.getDefault().resumeAppSwitches(); } catch (RemoteException e) { } final boolean isActivity = pendingIntent.isActivity(); if (isActivity) { final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( mContext, pendingIntent.getIntent(), mCurrentUserId); dismissKeyguardThenExecute(new OnDismissAction() { @Override public boolean onDismiss() { if (keyguardShowing && !afterKeyguardGone) { try { ActivityManagerNative.getDefault() .keyguardWaitingForActivityDrawn(); ActivityManagerNative.getDefault().resumeAppSwitches(); } catch (RemoteException e) { } } boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent); overrideActivityPendingAppTransition(keyguardShowing && !afterKeyguardGone); // close the shade if it was open if (handled) { animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); visibilityChanged(false); mAssistManager.hideAssist(); } // Wait for activity start. return handled; } }, afterKeyguardGone); return true; } else { return super.onClickHandler(view, pendingIntent, fillInIntent); } } private void logActionClick(View view) { ViewParent parent = view.getParent(); String key = getNotificationKeyForParent(parent); if (key == null) { Log.w(TAG, "Couldn't determine notification for click."); return; } int index = -1; // If this is a default template, determine the index of the button. if (view.getId() == com.android.internal.R.id.action0 && parent != null && parent instanceof ViewGroup) { ViewGroup actionGroup = (ViewGroup) parent; index = actionGroup.indexOfChild(view); } if (NOTIFICATION_CLICK_DEBUG) { Log.d(TAG, "Clicked on button " + index + " for " + key); } try { mBarService.onNotificationActionClick(key, index); } catch (RemoteException e) { // Ignore } } private String getNotificationKeyForParent(ViewParent parent) { while (parent != null) { if (parent instanceof ExpandableNotificationRow) { return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey(); } parent = parent.getParent(); } return null; } private boolean superOnClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { return super.onClickHandler(view, pendingIntent, fillInIntent); } }; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_USER_SWITCHED.equals(action)) { mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); updateCurrentProfilesCache(); if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); updateLockscreenNotificationSetting(); userSwitched(mCurrentUserId); } else if (Intent.ACTION_USER_ADDED.equals(action)) { updateCurrentProfilesCache(); } else if (Intent.ACTION_USER_PRESENT.equals(action)) { List recentTask = null; try { recentTask = ActivityManagerNative.getDefault().getRecentTasks(1, ActivityManager.RECENT_WITH_EXCLUDED | ActivityManager.RECENT_INCLUDE_PROFILES, mCurrentUserId); } catch (RemoteException e) { // Abandon hope activity manager not running. } if (recentTask != null && recentTask.size() > 0) { UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId); if (user != null && user.isManagedProfile()) { Toast toast = Toast.makeText(mContext, R.string.managed_profile_foreground_toast, Toast.LENGTH_SHORT); TextView text = (TextView) toast.getView().findViewById( android.R.id.message); text.setCompoundDrawablesRelativeWithIntrinsicBounds( R.drawable.stat_sys_managed_profile_status, 0, 0, 0); int paddingPx = mContext.getResources().getDimensionPixelSize( R.dimen.managed_profile_toast_padding); text.setCompoundDrawablePadding(paddingPx); toast.show(); } } } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { NotificationManager noMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); noMan.cancel(R.id.notification_hidden); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); if (BANNER_ACTION_SETUP.equals(action)) { animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ); } } } }; private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) && isCurrentProfile(getSendingUserId())) { mUsersAllowingPrivateNotifications.clear(); updateLockscreenNotificationSetting(); updateNotifications(); } } }; private final NotificationListenerService mNotificationListener = new NotificationListenerService() { @Override public void onListenerConnected() { if (DEBUG) Log.d(TAG, "onListenerConnected"); final StatusBarNotification[] notifications = getActiveNotifications(); final RankingMap currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { for (StatusBarNotification sbn : notifications) { addNotification(sbn, currentRanking, null /* oldEntry */); } } }); } @Override public void onNotificationPosted(final StatusBarNotification sbn, final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); if (sbn != null) { mHandler.post(new Runnable() { @Override public void run() { String key = sbn.getKey(); boolean isUpdate = mNotificationData.get(key) != null; // In case we don't allow child notifications, we ignore children of // notifications that have a summary, since we're not going to show them // anyway. This is true also when the summary is canceled, // because children are automatically canceled by NoMan in that case. if (!ENABLE_CHILD_NOTIFICATIONS && mGroupManager.isChildInGroupWithSummary(sbn)) { if (DEBUG) { Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); } // Remove existing notification to avoid stale data. if (isUpdate) { removeNotification(key, rankingMap); } else { mNotificationData.updateRanking(rankingMap); } return; } if (isUpdate) { updateNotification(sbn, rankingMap); } else { addNotification(sbn, rankingMap, null /* oldEntry */); } } }); } } @Override public void onNotificationRemoved(StatusBarNotification sbn, final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); if (sbn != null) { final String key = sbn.getKey(); mHandler.post(new Runnable() { @Override public void run() { removeNotification(key, rankingMap); } }); } } @Override public void onNotificationRankingUpdate(final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onRankingUpdate"); if (rankingMap != null) { mHandler.post(new Runnable() { @Override public void run() { updateNotificationRanking(rankingMap); } }); } } }; private void updateCurrentProfilesCache() { synchronized (mCurrentProfiles) { mCurrentProfiles.clear(); if (mUserManager != null) { for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { mCurrentProfiles.put(user.id, user); } } } } public void start() { mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); mDisplay = mWindowManager.getDefaultDisplay(); mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService( Context.DEVICE_POLICY_SERVICE); mNotificationColorUtil = NotificationColorUtil.getInstance(mContext); mNotificationData = new NotificationData(this); mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.checkService(DreamService.DREAM_SERVICE)); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, mSettingsObserver); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, mSettingsObserver); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, mSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), true, mLockscreenSettingsObserver, UserHandle.USER_ALL); mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mRecents = getComponent(Recents.class); mRecents.setCallback(this); final Configuration currentConfig = mContext.getResources().getConfiguration(); mLocale = currentConfig.locale; mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); mFontScale = currentConfig.fontScale; mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); mFastOutLinearIn = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in); // Connect in to the status bar manager service StatusBarIconList iconList = new StatusBarIconList(); mCommandQueue = new CommandQueue(this, iconList); int[] switches = new int[8]; ArrayList binders = new ArrayList(); try { mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders); } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. } createAndAddWindows(); mSettingsObserver.onChange(false); // set up disable(switches[0], switches[6], false /* animate */); setSystemUiVisibility(switches[1], 0xffffffff); topAppWindowChanged(switches[2] != 0); // StatusBarManagerService has a back up of IME token and it's restored here. setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0); // Set up the initial icon state int N = iconList.size(); int viewIndex = 0; for (int i=0; i