• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.tablet;
18 
19 import java.io.FileDescriptor;
20 import java.io.PrintWriter;
21 import java.util.ArrayList;
22 
23 import android.animation.LayoutTransition;
24 import android.animation.ObjectAnimator;
25 import android.app.ActivityManagerNative;
26 import android.app.Dialog;
27 import android.app.KeyguardManager;
28 import android.app.PendingIntent;
29 import android.app.Notification;
30 import android.app.StatusBarManager;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.SharedPreferences;
34 import android.content.res.Configuration;
35 import android.content.res.Resources;
36 import android.inputmethodservice.InputMethodService;
37 import android.graphics.PixelFormat;
38 import android.graphics.Point;
39 import android.graphics.Rect;
40 import android.graphics.drawable.LayerDrawable;
41 import android.provider.Settings;
42 import android.os.Handler;
43 import android.os.IBinder;
44 import android.os.Message;
45 import android.os.RemoteException;
46 import android.os.ServiceManager;
47 import android.text.TextUtils;
48 import android.util.Slog;
49 import android.view.accessibility.AccessibilityEvent;
50 import android.view.Display;
51 import android.view.Gravity;
52 import android.view.IWindowManager;
53 import android.view.KeyEvent;
54 import android.view.LayoutInflater;
55 import android.view.MotionEvent;
56 import android.view.SoundEffectConstants;
57 import android.view.VelocityTracker;
58 import android.view.View;
59 import android.view.ViewConfiguration;
60 import android.view.ViewGroup;
61 import android.view.WindowManager;
62 import android.view.WindowManagerImpl;
63 import android.widget.ImageView;
64 import android.widget.LinearLayout;
65 import android.widget.RemoteViews;
66 import android.widget.ScrollView;
67 import android.widget.TextView;
68 
69 import com.android.internal.statusbar.StatusBarIcon;
70 import com.android.internal.statusbar.StatusBarNotification;
71 
72 import com.android.systemui.R;
73 import com.android.systemui.statusbar.*;
74 import com.android.systemui.statusbar.policy.BatteryController;
75 import com.android.systemui.statusbar.policy.BluetoothController;
76 import com.android.systemui.statusbar.policy.CompatModeButton;
77 import com.android.systemui.statusbar.policy.LocationController;
78 import com.android.systemui.statusbar.policy.NetworkController;
79 import com.android.systemui.statusbar.policy.Prefs;
80 import com.android.systemui.recent.RecentTasksLoader;
81 import com.android.systemui.recent.RecentsPanelView;
82 
83 public class TabletStatusBar extends StatusBar implements
84         HeightReceiver.OnBarHeightChangedListener,
85         InputMethodsPanel.OnHardKeyboardEnabledChangeListener {
86     public static final boolean DEBUG = false;
87     public static final boolean DEBUG_COMPAT_HELP = false;
88     public static final String TAG = "TabletStatusBar";
89 
90 
91     public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
92     public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
93     public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002;
94     public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003;
95     public static final int MSG_OPEN_RECENTS_PANEL = 1020;
96     public static final int MSG_CLOSE_RECENTS_PANEL = 1021;
97     public static final int MSG_SHOW_CHROME = 1030;
98     public static final int MSG_HIDE_CHROME = 1031;
99     public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040;
100     public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041;
101     public static final int MSG_OPEN_COMPAT_MODE_PANEL = 1050;
102     public static final int MSG_CLOSE_COMPAT_MODE_PANEL = 1051;
103     public static final int MSG_STOP_TICKER = 2000;
104 
105     // Fitts' Law assistance for LatinIME; see policy.EventHole
106     private static final boolean FAKE_SPACE_BAR = true;
107 
108     // Notification "peeking" (flyover preview of individual notifications)
109     final static boolean NOTIFICATION_PEEK_ENABLED = false;
110     final static int NOTIFICATION_PEEK_HOLD_THRESH = 200; // ms
111     final static int NOTIFICATION_PEEK_FADE_DELAY = 3000; // ms
112 
113     // The height of the bar, as definied by the build.  It may be taller if we're plugged
114     // into hdmi.
115     int mNaturalBarHeight = -1;
116     int mIconSize = -1;
117     int mIconHPadding = -1;
118     private int mMaxNotificationIcons = 5;
119 
120     H mHandler = new H();
121 
122     IWindowManager mWindowManager;
123 
124     // tracking all current notifications
125     private NotificationData mNotificationData = new NotificationData();
126 
127     TabletStatusBarView mStatusBarView;
128     View mNotificationArea;
129     View mNotificationTrigger;
130     NotificationIconArea mNotificationIconArea;
131     ViewGroup mNavigationArea;
132 
133     boolean mNotificationDNDMode;
134     NotificationData.Entry mNotificationDNDDummyEntry;
135 
136     ImageView mBackButton;
137     View mHomeButton;
138     View mMenuButton;
139     View mRecentButton;
140 
141     ViewGroup mFeedbackIconArea; // notification icons, IME icon, compat icon
142     InputMethodButton mInputMethodSwitchButton;
143     CompatModeButton mCompatModeButton;
144 
145     NotificationPanel mNotificationPanel;
146     WindowManager.LayoutParams mNotificationPanelParams;
147     NotificationPeekPanel mNotificationPeekWindow;
148     ViewGroup mNotificationPeekRow;
149     int mNotificationPeekIndex;
150     IBinder mNotificationPeekKey;
151     LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight;
152 
153     int mNotificationPeekTapDuration;
154     int mNotificationFlingVelocity;
155 
156     ViewGroup mPile;
157 
158     HeightReceiver mHeightReceiver;
159     BatteryController mBatteryController;
160     BluetoothController mBluetoothController;
161     LocationController mLocationController;
162     NetworkController mNetworkController;
163 
164     ViewGroup mBarContents;
165     LayoutTransition mBarContentsLayoutTransition;
166 
167     // hide system chrome ("lights out") support
168     View mShadow;
169 
170     NotificationIconArea.IconLayout mIconLayout;
171 
172     TabletTicker mTicker;
173 
174     View mFakeSpaceBar;
175     KeyEvent mSpaceBarKeyEvent = null;
176 
177     View mCompatibilityHelpDialog = null;
178 
179     // for disabling the status bar
180     int mDisabled = 0;
181 
182     private RecentsPanelView mRecentsPanel;
183     private RecentTasksLoader mRecentTasksLoader;
184     private InputMethodsPanel mInputMethodsPanel;
185     private CompatModePanel mCompatModePanel;
186 
187     private int mSystemUiVisibility = 0;
188     // used to notify status bar for suppressing notification LED
189     private boolean mPanelSlightlyVisible;
190 
getContext()191     public Context getContext() { return mContext; }
192 
addPanelWindows()193     protected void addPanelWindows() {
194         final Context context = mContext;
195         final Resources res = mContext.getResources();
196 
197         // Notification Panel
198         mNotificationPanel = (NotificationPanel)View.inflate(context,
199                 R.layout.status_bar_notification_panel, null);
200         mNotificationPanel.setBar(this);
201         mNotificationPanel.show(false, false);
202         mNotificationPanel.setOnTouchListener(
203                 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel));
204 
205         // the battery icon
206         mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery));
207         mBatteryController.addLabelView(
208                 (TextView)mNotificationPanel.findViewById(R.id.battery_text));
209 
210         // Bt
211         mBluetoothController.addIconView(
212                 (ImageView)mNotificationPanel.findViewById(R.id.bluetooth));
213 
214         // network icons: either a combo icon that switches between mobile and data, or distinct
215         // mobile and data icons
216         final ImageView comboRSSI =
217                 (ImageView)mNotificationPanel.findViewById(R.id.network_signal);
218         if (comboRSSI != null) {
219             mNetworkController.addCombinedSignalIconView(comboRSSI);
220         }
221         final ImageView mobileRSSI =
222                 (ImageView)mNotificationPanel.findViewById(R.id.mobile_signal);
223         if (mobileRSSI != null) {
224             mNetworkController.addPhoneSignalIconView(mobileRSSI);
225         }
226         final ImageView wifiRSSI =
227                 (ImageView)mNotificationPanel.findViewById(R.id.wifi_signal);
228         if (wifiRSSI != null) {
229             mNetworkController.addWifiIconView(wifiRSSI);
230         }
231 
232         mNetworkController.addDataTypeIconView(
233                 (ImageView)mNotificationPanel.findViewById(R.id.network_type));
234         mNetworkController.addDataDirectionOverlayIconView(
235                 (ImageView)mNotificationPanel.findViewById(R.id.network_direction));
236         mNetworkController.addLabelView(
237                 (TextView)mNotificationPanel.findViewById(R.id.network_text));
238         mNetworkController.addLabelView(
239                 (TextView)mBarContents.findViewById(R.id.network_text));
240 
241         mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
242 
243         WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams(
244                 res.getDimensionPixelSize(R.dimen.notification_panel_width),
245                 getNotificationPanelHeight(),
246                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
247                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
248                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
249                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
250                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
251                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
252                 PixelFormat.TRANSLUCENT);
253         lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
254         lp.setTitle("NotificationPanel");
255         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
256                 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
257         lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation
258 //        lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
259 
260         WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
261 
262         // Notification preview window
263         if (NOTIFICATION_PEEK_ENABLED) {
264             mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context,
265                     R.layout.status_bar_notification_peek, null);
266             mNotificationPeekWindow.setBar(this);
267 
268             mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content);
269             mNotificationPeekWindow.setVisibility(View.GONE);
270             mNotificationPeekWindow.setOnTouchListener(
271                     new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow));
272             mNotificationPeekScrubRight = new LayoutTransition();
273             mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING,
274                     ObjectAnimator.ofInt(null, "left", -512, 0));
275             mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING,
276                     ObjectAnimator.ofInt(null, "left", -512, 0));
277             mNotificationPeekScrubRight.setDuration(500);
278 
279             mNotificationPeekScrubLeft = new LayoutTransition();
280             mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING,
281                     ObjectAnimator.ofInt(null, "left", 512, 0));
282             mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING,
283                     ObjectAnimator.ofInt(null, "left", 512, 0));
284             mNotificationPeekScrubLeft.setDuration(500);
285 
286             // XXX: setIgnoreChildren?
287             lp = new WindowManager.LayoutParams(
288                     512, // ViewGroup.LayoutParams.WRAP_CONTENT,
289                     ViewGroup.LayoutParams.WRAP_CONTENT,
290                     WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
291                     WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
292                         | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
293                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
294                     PixelFormat.TRANSLUCENT);
295             lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
296             lp.y = res.getDimensionPixelOffset(R.dimen.peek_window_y_offset);
297             lp.setTitle("NotificationPeekWindow");
298             lp.windowAnimations = com.android.internal.R.style.Animation_Toast;
299 
300             WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp);
301         }
302 
303         // Recents Panel
304         mRecentTasksLoader = new RecentTasksLoader(context);
305         mRecentsPanel = (RecentsPanelView) View.inflate(context,
306                 R.layout.status_bar_recent_panel, null);
307         mRecentsPanel.setVisibility(View.GONE);
308         mRecentsPanel.setSystemUiVisibility(View.STATUS_BAR_DISABLE_BACK);
309         mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
310                 mRecentsPanel));
311         mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader);
312         mRecentTasksLoader.setRecentsPanel(mRecentsPanel);
313         mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel);
314 
315         lp = new WindowManager.LayoutParams(
316                 (int) res.getDimension(R.dimen.status_bar_recents_width),
317                 ViewGroup.LayoutParams.MATCH_PARENT,
318                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
319                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
320                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
321                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
322                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
323                 PixelFormat.TRANSLUCENT);
324         lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
325         lp.setTitle("RecentsPanel");
326         lp.windowAnimations = R.style.Animation_RecentPanel;
327         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
328                 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
329 
330         WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
331         mRecentsPanel.setBar(this);
332 
333         // Input methods Panel
334         mInputMethodsPanel = (InputMethodsPanel) View.inflate(context,
335                 R.layout.status_bar_input_methods_panel, null);
336         mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this);
337         mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener(
338                 MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel));
339         mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton);
340         mStatusBarView.setIgnoreChildren(3, mInputMethodSwitchButton, mInputMethodsPanel);
341         lp = new WindowManager.LayoutParams(
342                 ViewGroup.LayoutParams.WRAP_CONTENT,
343                 ViewGroup.LayoutParams.WRAP_CONTENT,
344                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
345                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
346                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
347                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
348                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
349                 PixelFormat.TRANSLUCENT);
350         lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
351         lp.setTitle("InputMethodsPanel");
352         lp.windowAnimations = R.style.Animation_RecentPanel;
353 
354         WindowManagerImpl.getDefault().addView(mInputMethodsPanel, lp);
355 
356         // Compatibility mode selector panel
357         mCompatModePanel = (CompatModePanel) View.inflate(context,
358                 R.layout.status_bar_compat_mode_panel, null);
359         mCompatModePanel.setOnTouchListener(new TouchOutsideListener(
360                 MSG_CLOSE_COMPAT_MODE_PANEL, mCompatModePanel));
361         mCompatModePanel.setTrigger(mCompatModeButton);
362         mCompatModePanel.setVisibility(View.GONE);
363         mStatusBarView.setIgnoreChildren(4, mCompatModeButton, mCompatModePanel);
364         lp = new WindowManager.LayoutParams(
365                 250,
366                 ViewGroup.LayoutParams.WRAP_CONTENT,
367                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
368                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
369                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
370                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
371                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
372                 PixelFormat.TRANSLUCENT);
373         lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
374         lp.setTitle("CompatModePanel");
375         lp.windowAnimations = android.R.style.Animation_Dialog;
376 
377         WindowManagerImpl.getDefault().addView(mCompatModePanel, lp);
378     }
379 
getNotificationPanelHeight()380     private int getNotificationPanelHeight() {
381         final Resources res = mContext.getResources();
382         final Display d = WindowManagerImpl.getDefault().getDefaultDisplay();
383         final Point size = new Point();
384         d.getRealSize(size);
385         return Math.max(res.getDimensionPixelSize(R.dimen.notification_panel_min_height), size.y);
386     }
387 
388     @Override
start()389     public void start() {
390         super.start(); // will add the main bar view
391     }
392 
393     @Override
onConfigurationChanged(Configuration newConfig)394     protected void onConfigurationChanged(Configuration newConfig) {
395         mHeightReceiver.updateHeight(); // display size may have changed
396         loadDimens();
397         mNotificationPanelParams.height = getNotificationPanelHeight();
398         WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel,
399                 mNotificationPanelParams);
400         mRecentsPanel.updateValuesFromResources();
401     }
402 
loadDimens()403     protected void loadDimens() {
404         final Resources res = mContext.getResources();
405 
406         mNaturalBarHeight = res.getDimensionPixelSize(
407                 com.android.internal.R.dimen.system_bar_height);
408 
409         int newIconSize = res.getDimensionPixelSize(
410             com.android.internal.R.dimen.system_bar_icon_size);
411         int newIconHPadding = res.getDimensionPixelSize(
412             R.dimen.status_bar_icon_padding);
413 
414         if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) {
415 //            Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding);
416             mIconHPadding = newIconHPadding;
417             mIconSize = newIconSize;
418             reloadAllNotificationIcons(); // reload the tray
419         }
420 
421         final int numIcons = res.getInteger(R.integer.config_maxNotificationIcons);
422         if (numIcons != mMaxNotificationIcons) {
423             mMaxNotificationIcons = numIcons;
424             if (DEBUG) Slog.d(TAG, "max notification icons: " + mMaxNotificationIcons);
425             reloadAllNotificationIcons();
426         }
427     }
428 
makeStatusBarView()429     protected View makeStatusBarView() {
430         final Context context = mContext;
431 
432         mWindowManager = IWindowManager.Stub.asInterface(
433                 ServiceManager.getService(Context.WINDOW_SERVICE));
434 
435         // This guy will listen for HDMI plugged broadcasts so we can resize the
436         // status bar as appropriate.
437         mHeightReceiver = new HeightReceiver(mContext);
438         mHeightReceiver.registerReceiver();
439         loadDimens();
440 
441         final TabletStatusBarView sb = (TabletStatusBarView)View.inflate(
442                 context, R.layout.status_bar, null);
443         mStatusBarView = sb;
444 
445         sb.setHandler(mHandler);
446 
447         try {
448             // Sanity-check that someone hasn't set up the config wrong and asked for a navigation
449             // bar on a tablet that has only the system bar
450             if (mWindowManager.hasNavigationBar()) {
451                 throw new RuntimeException(
452                         "Tablet device cannot show navigation bar and system bar");
453             }
454         } catch (RemoteException ex) {
455         }
456 
457         mBarContents = (ViewGroup) sb.findViewById(R.id.bar_contents);
458         // layout transitions for the status bar's contents
459         mBarContentsLayoutTransition = new LayoutTransition();
460         // add/removal will fade as normal
461         mBarContentsLayoutTransition.setAnimator(LayoutTransition.APPEARING,
462                 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
463         mBarContentsLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING,
464                 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
465         // no animations for siblings on change: just jump into place please
466         mBarContentsLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, null);
467         mBarContentsLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null);
468         // quick like bunny
469         mBarContentsLayoutTransition.setDuration(250 * (DEBUG?10:1));
470         mBarContents.setLayoutTransition(mBarContentsLayoutTransition);
471 
472         // the whole right-hand side of the bar
473         mNotificationArea = sb.findViewById(R.id.notificationArea);
474         if (!NOTIFICATION_PEEK_ENABLED) {
475             mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener());
476         }
477 
478         // the button to open the notification area
479         mNotificationTrigger = sb.findViewById(R.id.notificationTrigger);
480         if (NOTIFICATION_PEEK_ENABLED) {
481             mNotificationTrigger.setOnTouchListener(new NotificationTriggerTouchListener());
482         }
483 
484         // the more notifications icon
485         mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
486 
487         // where the icons go
488         mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
489         if (NOTIFICATION_PEEK_ENABLED) {
490             mIconLayout.setOnTouchListener(new NotificationIconTouchListener());
491         }
492 
493         ViewConfiguration vc = ViewConfiguration.get(context);
494         mNotificationPeekTapDuration = vc.getTapTimeout();
495         mNotificationFlingVelocity = 300; // px/s
496 
497         mTicker = new TabletTicker(this);
498 
499         // The icons
500         mLocationController = new LocationController(mContext); // will post a notification
501 
502         mBatteryController = new BatteryController(mContext);
503         mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
504         mBluetoothController = new BluetoothController(mContext);
505         mBluetoothController.addIconView((ImageView)sb.findViewById(R.id.bluetooth));
506 
507         mNetworkController = new NetworkController(mContext);
508         final SignalClusterView signalCluster =
509                 (SignalClusterView)sb.findViewById(R.id.signal_cluster);
510         mNetworkController.addSignalCluster(signalCluster);
511 
512         // The navigation buttons
513         mBackButton = (ImageView)sb.findViewById(R.id.back);
514         mNavigationArea = (ViewGroup) sb.findViewById(R.id.navigationArea);
515         mHomeButton = mNavigationArea.findViewById(R.id.home);
516         mMenuButton = mNavigationArea.findViewById(R.id.menu);
517         mRecentButton = mNavigationArea.findViewById(R.id.recent_apps);
518         mRecentButton.setOnClickListener(mOnClickListener);
519         mNavigationArea.setLayoutTransition(mBarContentsLayoutTransition);
520         // no multi-touch on the nav buttons
521         mNavigationArea.setMotionEventSplittingEnabled(false);
522 
523         // The bar contents buttons
524         mFeedbackIconArea = (ViewGroup)sb.findViewById(R.id.feedbackIconArea);
525         mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton);
526         // Overwrite the lister
527         mInputMethodSwitchButton.setOnClickListener(mOnClickListener);
528 
529         mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton);
530         mCompatModeButton.setOnClickListener(mOnClickListener);
531 
532         // for redirecting errant bar taps to the IME
533         mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar);
534 
535         // "shadows" of the status bar features, for lights-out mode
536         mShadow = sb.findViewById(R.id.bar_shadow);
537         mShadow.setOnTouchListener(
538             new View.OnTouchListener() {
539                 public boolean onTouch(View v, MotionEvent ev) {
540                     if (ev.getAction() == MotionEvent.ACTION_DOWN) {
541                         // even though setting the systemUI visibility below will turn these views
542                         // on, we need them to come up faster so that they can catch this motion
543                         // event
544                         mShadow.setVisibility(View.GONE);
545                         mBarContents.setVisibility(View.VISIBLE);
546 
547                         try {
548                             mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
549                         } catch (RemoteException ex) {
550                             // system process dead
551                         }
552                     }
553                     return false;
554                 }
555             });
556 
557         // tuning parameters
558         final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 600;
559         final int LIGHTS_GOING_OUT_SHADOW_DURATION = 1000;
560         final int LIGHTS_GOING_OUT_SHADOW_DELAY    = 500;
561 
562         final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200;
563 //        final int LIGHTS_COMING_UP_SYSBAR_DELAY    = 50;
564         final int LIGHTS_COMING_UP_SHADOW_DURATION = 0;
565 
566         LayoutTransition xition = new LayoutTransition();
567         xition.setAnimator(LayoutTransition.APPEARING,
568                ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f));
569         xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION);
570         xition.setStartDelay(LayoutTransition.APPEARING, 0);
571         xition.setAnimator(LayoutTransition.DISAPPEARING,
572                ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
573         xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION);
574         xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
575         ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition);
576 
577         xition = new LayoutTransition();
578         xition.setAnimator(LayoutTransition.APPEARING,
579                ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
580         xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION);
581         xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY);
582         xition.setAnimator(LayoutTransition.DISAPPEARING,
583                ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
584         xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION);
585         xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
586         ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition);
587 
588         // set the initial view visibility
589         setAreThereNotifications();
590 
591         // Add the windows
592         addPanelWindows();
593         mRecentButton.setOnTouchListener(mRecentsPanel);
594 
595         mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
596         mPile.removeAllViews();
597 
598         ScrollView scroller = (ScrollView)mPile.getParent();
599         scroller.setFillViewport(true);
600 
601         mHeightReceiver.addOnBarHeightChangedListener(this);
602 
603         return sb;
604     }
605 
getStatusBarHeight()606     public int getStatusBarHeight() {
607         return mHeightReceiver.getHeight();
608     }
609 
getStatusBarGravity()610     protected int getStatusBarGravity() {
611         return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
612     }
613 
onBarHeightChanged(int height)614     public void onBarHeightChanged(int height) {
615         final WindowManager.LayoutParams lp
616                 = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams();
617         if (lp == null) {
618             // haven't been added yet
619             return;
620         }
621         if (lp.height != height) {
622             lp.height = height;
623             final WindowManager wm = WindowManagerImpl.getDefault();
624             wm.updateViewLayout(mStatusBarView, lp);
625         }
626     }
627 
628     private class H extends Handler {
handleMessage(Message m)629         public void handleMessage(Message m) {
630             switch (m.what) {
631                 case MSG_OPEN_NOTIFICATION_PEEK:
632                     if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1);
633 
634                     if (m.arg1 >= 0) {
635                         final int N = mNotificationData.size();
636 
637                         if (!mNotificationDNDMode) {
638                             if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
639                                 NotificationData.Entry entry = mNotificationData.get(N-1-mNotificationPeekIndex);
640                                 entry.icon.setBackgroundColor(0);
641                                 mNotificationPeekIndex = -1;
642                                 mNotificationPeekKey = null;
643                             }
644                         }
645 
646                         final int peekIndex = m.arg1;
647                         if (peekIndex < N) {
648                             //Slog.d(TAG, "loading peek: " + peekIndex);
649                             NotificationData.Entry entry =
650                                 mNotificationDNDMode
651                                     ? mNotificationDNDDummyEntry
652                                     : mNotificationData.get(N-1-peekIndex);
653                             NotificationData.Entry copy = new NotificationData.Entry(
654                                     entry.key,
655                                     entry.notification,
656                                     entry.icon);
657                             inflateViews(copy, mNotificationPeekRow);
658 
659                             if (mNotificationDNDMode) {
660                                 copy.content.setOnClickListener(new View.OnClickListener() {
661                                     public void onClick(View v) {
662                                         SharedPreferences.Editor editor = Prefs.edit(mContext);
663                                         editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false);
664                                         editor.apply();
665                                         animateCollapse();
666                                         visibilityChanged(false);
667                                     }
668                                 });
669                             }
670 
671                             entry.icon.setBackgroundColor(0x20FFFFFF);
672 
673 //                          mNotificationPeekRow.setLayoutTransition(
674 //                              peekIndex < mNotificationPeekIndex
675 //                                  ? mNotificationPeekScrubLeft
676 //                                  : mNotificationPeekScrubRight);
677 
678                             mNotificationPeekRow.removeAllViews();
679                             mNotificationPeekRow.addView(copy.row);
680 
681                             mNotificationPeekWindow.setVisibility(View.VISIBLE);
682                             mNotificationPanel.show(false, true);
683 
684                             mNotificationPeekIndex = peekIndex;
685                             mNotificationPeekKey = entry.key;
686                         }
687                     }
688                     break;
689                 case MSG_CLOSE_NOTIFICATION_PEEK:
690                     if (DEBUG) Slog.d(TAG, "closing notification peek window");
691                     mNotificationPeekWindow.setVisibility(View.GONE);
692                     mNotificationPeekRow.removeAllViews();
693 
694                     final int N = mNotificationData.size();
695                     if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
696                         NotificationData.Entry entry =
697                             mNotificationDNDMode
698                                 ? mNotificationDNDDummyEntry
699                                 : mNotificationData.get(N-1-mNotificationPeekIndex);
700                         entry.icon.setBackgroundColor(0);
701                     }
702 
703                     mNotificationPeekIndex = -1;
704                     mNotificationPeekKey = null;
705                     break;
706                 case MSG_OPEN_NOTIFICATION_PANEL:
707                     if (DEBUG) Slog.d(TAG, "opening notifications panel");
708                     if (!mNotificationPanel.isShowing()) {
709                         if (NOTIFICATION_PEEK_ENABLED) {
710                             mNotificationPeekWindow.setVisibility(View.GONE);
711                         }
712                         mNotificationPanel.show(true, true);
713                         mNotificationArea.setVisibility(View.INVISIBLE);
714                         mTicker.halt();
715                     }
716                     break;
717                 case MSG_CLOSE_NOTIFICATION_PANEL:
718                     if (DEBUG) Slog.d(TAG, "closing notifications panel");
719                     if (mNotificationPanel.isShowing()) {
720                         mNotificationPanel.show(false, true);
721                         mNotificationArea.setVisibility(View.VISIBLE);
722                     }
723                     break;
724                 case MSG_OPEN_RECENTS_PANEL:
725                     if (DEBUG) Slog.d(TAG, "opening recents panel");
726                     if (mRecentsPanel != null) {
727                         mRecentsPanel.show(true, true);
728                     }
729                     break;
730                 case MSG_CLOSE_RECENTS_PANEL:
731                     if (DEBUG) Slog.d(TAG, "closing recents panel");
732                     if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
733                         mRecentsPanel.show(false, true);
734                     }
735                     break;
736                 case MSG_OPEN_INPUT_METHODS_PANEL:
737                     if (DEBUG) Slog.d(TAG, "opening input methods panel");
738                     if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel();
739                     break;
740                 case MSG_CLOSE_INPUT_METHODS_PANEL:
741                     if (DEBUG) Slog.d(TAG, "closing input methods panel");
742                     if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false);
743                     break;
744                 case MSG_OPEN_COMPAT_MODE_PANEL:
745                     if (DEBUG) Slog.d(TAG, "opening compat panel");
746                     if (mCompatModePanel != null) mCompatModePanel.openPanel();
747                     break;
748                 case MSG_CLOSE_COMPAT_MODE_PANEL:
749                     if (DEBUG) Slog.d(TAG, "closing compat panel");
750                     if (mCompatModePanel != null) mCompatModePanel.closePanel();
751                     break;
752                 case MSG_SHOW_CHROME:
753                     if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)");
754                     mBarContents.setVisibility(View.VISIBLE);
755                     mShadow.setVisibility(View.GONE);
756                     mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
757                     notifyUiVisibilityChanged();
758                     break;
759                 case MSG_HIDE_CHROME:
760                     if (DEBUG) Slog.d(TAG, "showing shadows (lights out)");
761                     animateCollapse();
762                     visibilityChanged(false);
763                     mBarContents.setVisibility(View.GONE);
764                     mShadow.setVisibility(View.VISIBLE);
765                     mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
766                     notifyUiVisibilityChanged();
767                     break;
768                 case MSG_STOP_TICKER:
769                     mTicker.halt();
770                     break;
771             }
772         }
773     }
774 
addIcon(String slot, int index, int viewIndex, StatusBarIcon icon)775     public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
776         if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon);
777     }
778 
updateIcon(String slot, int index, int viewIndex, StatusBarIcon old, StatusBarIcon icon)779     public void updateIcon(String slot, int index, int viewIndex,
780             StatusBarIcon old, StatusBarIcon icon) {
781         if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon);
782     }
783 
removeIcon(String slot, int index, int viewIndex)784     public void removeIcon(String slot, int index, int viewIndex) {
785         if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")");
786     }
787 
addNotification(IBinder key, StatusBarNotification notification)788     public void addNotification(IBinder key, StatusBarNotification notification) {
789         if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
790         addNotificationViews(key, notification);
791 
792         final boolean immersive = isImmersive();
793         if (false && immersive) {
794             // TODO: immersive mode popups for tablet
795         } else if (notification.notification.fullScreenIntent != null) {
796             // not immersive & a full-screen alert should be shown
797             Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;"
798                     + " sending fullScreenIntent");
799             try {
800                 notification.notification.fullScreenIntent.send();
801             } catch (PendingIntent.CanceledException e) {
802             }
803         } else {
804             tick(key, notification, true);
805         }
806 
807         setAreThereNotifications();
808     }
809 
updateNotification(IBinder key, StatusBarNotification notification)810     public void updateNotification(IBinder key, StatusBarNotification notification) {
811         if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
812 
813         final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
814         if (oldEntry == null) {
815             Slog.w(TAG, "updateNotification for unknown key: " + key);
816             return;
817         }
818 
819         final StatusBarNotification oldNotification = oldEntry.notification;
820         final RemoteViews oldContentView = oldNotification.notification.contentView;
821 
822         final RemoteViews contentView = notification.notification.contentView;
823 
824         if (DEBUG) {
825             Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
826                     + " ongoing=" + oldNotification.isOngoing()
827                     + " expanded=" + oldEntry.expanded
828                     + " contentView=" + oldContentView
829                     + " rowParent=" + oldEntry.row.getParent());
830             Slog.d(TAG, "new notification: when=" + notification.notification.when
831                     + " ongoing=" + oldNotification.isOngoing()
832                     + " contentView=" + contentView);
833         }
834 
835         // Can we just reapply the RemoteViews in place?  If when didn't change, the order
836         // didn't change.
837         boolean contentsUnchanged = oldEntry.expanded != null
838                 && contentView != null && oldContentView != null
839                 && contentView.getPackage() != null
840                 && oldContentView.getPackage() != null
841                 && oldContentView.getPackage().equals(contentView.getPackage())
842                 && oldContentView.getLayoutId() == contentView.getLayoutId();
843         ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
844         boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
845                 && notification.priority == oldNotification.priority;
846                 // priority now encompasses isOngoing()
847         boolean updateTicker = notification.notification.tickerText != null
848                 && !TextUtils.equals(notification.notification.tickerText,
849                         oldEntry.notification.notification.tickerText);
850         boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1;
851         if (contentsUnchanged && (orderUnchanged || isLastAnyway)) {
852             if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
853             oldEntry.notification = notification;
854             try {
855                 // Reapply the RemoteViews
856                 contentView.reapply(mContext, oldEntry.content);
857                 // update the contentIntent
858                 final PendingIntent contentIntent = notification.notification.contentIntent;
859                 if (contentIntent != null) {
860                     final View.OnClickListener listener = new NotificationClicker(contentIntent,
861                             notification.pkg, notification.tag, notification.id);
862                     oldEntry.largeIcon.setOnClickListener(listener);
863                     oldEntry.content.setOnClickListener(listener);
864                 } else {
865                     oldEntry.largeIcon.setOnClickListener(null);
866                     oldEntry.content.setOnClickListener(null);
867                 }
868                 // Update the icon.
869                 final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
870                         notification.notification.icon, notification.notification.iconLevel,
871                         notification.notification.number,
872                         notification.notification.tickerText);
873                 if (!oldEntry.icon.set(ic)) {
874                     handleNotificationError(key, notification, "Couldn't update icon: " + ic);
875                     return;
876                 }
877                 // Update the large icon
878                 if (notification.notification.largeIcon != null) {
879                     oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon);
880                 } else {
881                     oldEntry.largeIcon.getLayoutParams().width = 0;
882                     oldEntry.largeIcon.setVisibility(View.INVISIBLE);
883                 }
884 
885                 if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) {
886                     // must update the peek window
887                     Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
888                     peekMsg.arg1 = mNotificationPeekIndex;
889                     mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
890                     mHandler.sendMessage(peekMsg);
891                 }
892             }
893             catch (RuntimeException e) {
894                 // It failed to add cleanly.  Log, and remove the view from the panel.
895                 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
896                 removeNotificationViews(key);
897                 addNotificationViews(key, notification);
898             }
899         } else {
900             if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
901             removeNotificationViews(key);
902             addNotificationViews(key, notification);
903         }
904 
905         // Restart the ticker if it's still running
906         if (updateTicker) {
907             mTicker.halt();
908             tick(key, notification, false);
909         }
910 
911         setAreThereNotifications();
912     }
913 
removeNotification(IBinder key)914     public void removeNotification(IBinder key) {
915         if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ")");
916         removeNotificationViews(key);
917         mTicker.remove(key);
918         setAreThereNotifications();
919     }
920 
showClock(boolean show)921     public void showClock(boolean show) {
922         View clock = mBarContents.findViewById(R.id.clock);
923         View network_text = mBarContents.findViewById(R.id.network_text);
924         if (clock != null) {
925             clock.setVisibility(show ? View.VISIBLE : View.GONE);
926         }
927         if (network_text != null) {
928             network_text.setVisibility((!show) ? View.VISIBLE : View.GONE);
929         }
930     }
931 
disable(int state)932     public void disable(int state) {
933         int old = mDisabled;
934         int diff = state ^ old;
935         mDisabled = state;
936 
937         // act accordingly
938         if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
939             boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
940             Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes"));
941             showClock(show);
942         }
943         if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
944             boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0;
945             Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes"));
946             mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE);
947         }
948         if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
949             if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
950                 Slog.i(TAG, "DISABLE_EXPAND: yes");
951                 animateCollapse();
952                 visibilityChanged(false);
953             }
954         }
955         if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
956             mNotificationDNDMode = Prefs.read(mContext)
957                         .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT);
958 
959             if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
960                 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":""));
961                 mTicker.halt();
962             } else {
963                 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":""));
964             }
965 
966             // refresh icons to show either notifications or the DND message
967             reloadAllNotificationIcons();
968         } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
969             if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
970                 mTicker.halt();
971             }
972         }
973         if ((diff & (StatusBarManager.DISABLE_RECENT
974                         | StatusBarManager.DISABLE_BACK
975                         | StatusBarManager.DISABLE_HOME)) != 0) {
976             setNavigationVisibility(state);
977         }
978     }
979 
setNavigationVisibility(int visibility)980     private void setNavigationVisibility(int visibility) {
981         boolean disableHome = ((visibility & StatusBarManager.DISABLE_HOME) != 0);
982         boolean disableRecent = ((visibility & StatusBarManager.DISABLE_RECENT) != 0);
983         boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0);
984 
985         mBackButton.setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
986         mHomeButton.setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
987         mRecentButton.setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
988 
989         mInputMethodSwitchButton.setScreenLocked(
990                 (visibility & StatusBarManager.DISABLE_SYSTEM_INFO) != 0);
991     }
992 
hasTicker(Notification n)993     private boolean hasTicker(Notification n) {
994         return n.tickerView != null || !TextUtils.isEmpty(n.tickerText);
995     }
996 
tick(IBinder key, StatusBarNotification n, boolean firstTime)997     private void tick(IBinder key, StatusBarNotification n, boolean firstTime) {
998         // Don't show the ticker when the windowshade is open.
999         if (mNotificationPanel.isShowing()) {
1000             return;
1001         }
1002         // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification
1003         // if it's a new notification.
1004         if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) {
1005             return;
1006         }
1007         // Show the ticker if one is requested. Also don't do this
1008         // until status bar window is attached to the window manager,
1009         // because...  well, what's the point otherwise?  And trying to
1010         // run a ticker without being attached will crash!
1011         if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
1012             if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
1013                             | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
1014                 mTicker.add(key, n);
1015                 mFeedbackIconArea.setVisibility(View.GONE);
1016             }
1017         }
1018     }
1019 
1020     // called by TabletTicker when it's done with all queued ticks
doneTicking()1021     public void doneTicking() {
1022         mFeedbackIconArea.setVisibility(View.VISIBLE);
1023     }
1024 
animateExpand()1025     public void animateExpand() {
1026         if (NOTIFICATION_PEEK_ENABLED) {
1027             mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
1028             mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1029             mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1030         }
1031         mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
1032         mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
1033     }
1034 
animateCollapse()1035     public void animateCollapse() {
1036         mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
1037         mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
1038         mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
1039         mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
1040         mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL);
1041         mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL);
1042         mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL);
1043         mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL);
1044         if (NOTIFICATION_PEEK_ENABLED) {
1045             mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
1046             mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1047         }
1048     }
1049 
1050     /**
1051      * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
1052      * This was added last-minute and is inconsistent with the way the rest of the notifications
1053      * are handled, because the notification isn't really cancelled.  The lights are just
1054      * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
1055      * this is what he wants. (see bug 1131461)
1056      */
visibilityChanged(boolean visible)1057     void visibilityChanged(boolean visible) {
1058         if (mPanelSlightlyVisible != visible) {
1059             mPanelSlightlyVisible = visible;
1060             try {
1061                 mBarService.onPanelRevealed();
1062             } catch (RemoteException ex) {
1063                 // Won't fail unless the world has ended.
1064             }
1065         }
1066     }
1067 
notifyUiVisibilityChanged()1068     private void notifyUiVisibilityChanged() {
1069         try {
1070             mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility);
1071         } catch (RemoteException ex) {
1072         }
1073     }
1074 
1075     @Override // CommandQueue
setSystemUiVisibility(int vis)1076     public void setSystemUiVisibility(int vis) {
1077         if (vis != mSystemUiVisibility) {
1078             mSystemUiVisibility = vis;
1079 
1080             mHandler.removeMessages(MSG_HIDE_CHROME);
1081             mHandler.removeMessages(MSG_SHOW_CHROME);
1082             mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE)
1083                     ? MSG_SHOW_CHROME : MSG_HIDE_CHROME);
1084 
1085             notifyUiVisibilityChanged();
1086         }
1087     }
1088 
setLightsOn(boolean on)1089     public void setLightsOn(boolean on) {
1090         // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app
1091         // that can't handle lights-out mode.
1092         if (mMenuButton.getVisibility() == View.VISIBLE) {
1093             on = true;
1094         }
1095 
1096         Slog.v(TAG, "setLightsOn(" + on + ")");
1097         if (on) {
1098             setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE);
1099         } else {
1100             setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE);
1101         }
1102     }
1103 
topAppWindowChanged(boolean showMenu)1104     public void topAppWindowChanged(boolean showMenu) {
1105         if (DEBUG) {
1106             Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button");
1107         }
1108         mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE);
1109 
1110         // See above re: lights-out policy for legacy apps.
1111         if (showMenu) setLightsOn(true);
1112 
1113         mCompatModeButton.refresh();
1114         if (mCompatModeButton.getVisibility() == View.VISIBLE) {
1115             if (DEBUG_COMPAT_HELP
1116                     || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) {
1117                 showCompatibilityHelp();
1118             }
1119         } else {
1120             hideCompatibilityHelp();
1121             mCompatModePanel.closePanel();
1122         }
1123     }
1124 
showCompatibilityHelp()1125     private void showCompatibilityHelp() {
1126         if (mCompatibilityHelpDialog != null) {
1127             return;
1128         }
1129 
1130         mCompatibilityHelpDialog = View.inflate(mContext, R.layout.compat_mode_help, null);
1131         View button = mCompatibilityHelpDialog.findViewById(R.id.button);
1132 
1133         button.setOnClickListener(new View.OnClickListener() {
1134             @Override
1135             public void onClick(View v) {
1136                 hideCompatibilityHelp();
1137                 SharedPreferences.Editor editor = Prefs.edit(mContext);
1138                 editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true);
1139                 editor.apply();
1140             }
1141         });
1142 
1143         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1144                 ViewGroup.LayoutParams.MATCH_PARENT,
1145                 ViewGroup.LayoutParams.MATCH_PARENT,
1146                 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
1147                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1148                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
1149                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
1150                 PixelFormat.TRANSLUCENT);
1151         lp.setTitle("CompatibilityModeDialog");
1152         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
1153                 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
1154         lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
1155 
1156         WindowManagerImpl.getDefault().addView(mCompatibilityHelpDialog, lp);
1157     }
1158 
hideCompatibilityHelp()1159     private void hideCompatibilityHelp() {
1160         if (mCompatibilityHelpDialog != null) {
1161             WindowManagerImpl.getDefault().removeView(mCompatibilityHelpDialog);
1162             mCompatibilityHelpDialog = null;
1163         }
1164     }
1165 
setImeWindowStatus(IBinder token, int vis, int backDisposition)1166     public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1167         mInputMethodSwitchButton.setImeWindowStatus(token,
1168                 (vis & InputMethodService.IME_ACTIVE) != 0);
1169         updateNotificationIcons();
1170         mInputMethodsPanel.setImeToken(token);
1171         int res;
1172         switch (backDisposition) {
1173             case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
1174                 res = R.drawable.ic_sysbar_back;
1175                 break;
1176             case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
1177                 res = R.drawable.ic_sysbar_back_ime;
1178                 break;
1179             case InputMethodService.BACK_DISPOSITION_DEFAULT:
1180             default:
1181                 if ((vis & InputMethodService.IME_VISIBLE) != 0) {
1182                     res = R.drawable.ic_sysbar_back_ime;
1183                 } else {
1184                     res = R.drawable.ic_sysbar_back;
1185                 }
1186                 break;
1187         }
1188         mBackButton.setImageResource(res);
1189         if (FAKE_SPACE_BAR) {
1190             mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0)
1191                     ? View.VISIBLE : View.GONE);
1192         }
1193     }
1194 
1195     @Override
setHardKeyboardStatus(boolean available, boolean enabled)1196     public void setHardKeyboardStatus(boolean available, boolean enabled) {
1197         if (DEBUG) {
1198             Slog.d(TAG, "Set hard keyboard status: available=" + available
1199                     + ", enabled=" + enabled);
1200         }
1201         mInputMethodSwitchButton.setHardKeyboardStatus(available);
1202         updateNotificationIcons();
1203         mInputMethodsPanel.setHardKeyboardStatus(available, enabled);
1204     }
1205 
1206     @Override
onHardKeyboardEnabledChange(boolean enabled)1207     public void onHardKeyboardEnabledChange(boolean enabled) {
1208         try {
1209             mBarService.setHardKeyboardEnabled(enabled);
1210         } catch (RemoteException ex) {
1211         }
1212     }
1213 
isImmersive()1214     private boolean isImmersive() {
1215         try {
1216             return ActivityManagerNative.getDefault().isTopActivityImmersive();
1217             //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
1218         } catch (RemoteException ex) {
1219             // the end is nigh
1220             return false;
1221         }
1222     }
1223 
setAreThereNotifications()1224     private void setAreThereNotifications() {
1225         if (mNotificationPanel != null) {
1226             mNotificationPanel.setClearable(mNotificationData.hasClearableItems());
1227         }
1228     }
1229 
1230     /**
1231      * Cancel this notification and tell the status bar service about the failure. Hold no locks.
1232      */
handleNotificationError(IBinder key, StatusBarNotification n, String message)1233     void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
1234         removeNotification(key);
1235         try {
1236             mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
1237         } catch (RemoteException ex) {
1238             // The end is nigh.
1239         }
1240     }
1241 
sendKey(KeyEvent key)1242     private void sendKey(KeyEvent key) {
1243         try {
1244             if (DEBUG) Slog.d(TAG, "injecting key event: " + key);
1245             mWindowManager.injectInputEventNoWait(key);
1246         } catch (RemoteException ex) {
1247         }
1248     }
1249 
1250     private View.OnClickListener mOnClickListener = new View.OnClickListener() {
1251         public void onClick(View v) {
1252             if (v == mRecentButton) {
1253                 onClickRecentButton();
1254             } else if (v == mInputMethodSwitchButton) {
1255                 onClickInputMethodSwitchButton();
1256             } else if (v == mCompatModeButton) {
1257                 onClickCompatModeButton();
1258             }
1259         }
1260     };
1261 
onClickRecentButton()1262     public void onClickRecentButton() {
1263         if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled);
1264         if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
1265             int msg = (mRecentsPanel.getVisibility() == View.GONE)
1266                 ? MSG_OPEN_RECENTS_PANEL
1267                 : MSG_CLOSE_RECENTS_PANEL;
1268             mHandler.removeMessages(msg);
1269             mHandler.sendEmptyMessage(msg);
1270         }
1271     }
1272 
onClickInputMethodSwitchButton()1273     public void onClickInputMethodSwitchButton() {
1274         if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled);
1275         int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ?
1276                 MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL;
1277         mHandler.removeMessages(msg);
1278         mHandler.sendEmptyMessage(msg);
1279     }
1280 
onClickCompatModeButton()1281     public void onClickCompatModeButton() {
1282         int msg = (mCompatModePanel.getVisibility() == View.GONE) ?
1283                 MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL;
1284         mHandler.removeMessages(msg);
1285         mHandler.sendEmptyMessage(msg);
1286     }
1287 
makeClicker(PendingIntent intent, String pkg, String tag, int id)1288     public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
1289         return new NotificationClicker(intent, pkg, tag, id);
1290     }
1291 
1292     private class NotificationClicker implements View.OnClickListener {
1293         private PendingIntent mIntent;
1294         private String mPkg;
1295         private String mTag;
1296         private int mId;
1297 
NotificationClicker(PendingIntent intent, String pkg, String tag, int id)1298         NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
1299             mIntent = intent;
1300             mPkg = pkg;
1301             mTag = tag;
1302             mId = id;
1303         }
1304 
onClick(View v)1305         public void onClick(View v) {
1306             try {
1307                 // The intent we are sending is for the application, which
1308                 // won't have permission to immediately start an activity after
1309                 // the user switches to home.  We know it is safe to do at this
1310                 // point, so make sure new activity switches are now allowed.
1311                 ActivityManagerNative.getDefault().resumeAppSwitches();
1312                 // Also, notifications can be launched from the lock screen,
1313                 // so dismiss the lock screen when the activity starts.
1314                 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
1315             } catch (RemoteException e) {
1316             }
1317 
1318             if (mIntent != null) {
1319                 int[] pos = new int[2];
1320                 v.getLocationOnScreen(pos);
1321                 Intent overlay = new Intent();
1322                 overlay.setSourceBounds(
1323                         new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
1324                 try {
1325                     mIntent.send(mContext, 0, overlay);
1326 
1327                 } catch (PendingIntent.CanceledException e) {
1328                     // the stack trace isn't very helpful here.  Just log the exception message.
1329                     Slog.w(TAG, "Sending contentIntent failed: " + e);
1330                 }
1331 
1332                 KeyguardManager kgm =
1333                     (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
1334                 if (kgm != null) kgm.exitKeyguardSecurely(null);
1335             }
1336 
1337             try {
1338                 mBarService.onNotificationClick(mPkg, mTag, mId);
1339             } catch (RemoteException ex) {
1340                 // system process is dead if we're here.
1341             }
1342 
1343             // close the shade if it was open
1344             animateCollapse();
1345             visibilityChanged(false);
1346 
1347             // If this click was on the intruder alert, hide that instead
1348 //            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
1349         }
1350     }
1351 
removeNotificationViews(IBinder key)1352     StatusBarNotification removeNotificationViews(IBinder key) {
1353         NotificationData.Entry entry = mNotificationData.remove(key);
1354         if (entry == null) {
1355             Slog.w(TAG, "removeNotification for unknown key: " + key);
1356             return null;
1357         }
1358         // Remove the expanded view.
1359         ViewGroup rowParent = (ViewGroup)entry.row.getParent();
1360         if (rowParent != null) rowParent.removeView(entry.row);
1361 
1362         if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) {
1363             // must close the peek as well, since it's gone
1364             mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1365         }
1366         // Remove the icon.
1367 //        ViewGroup iconParent = (ViewGroup)entry.icon.getParent();
1368 //        if (iconParent != null) iconParent.removeView(entry.icon);
1369         updateNotificationIcons();
1370 
1371         return entry.notification;
1372     }
1373 
1374     private class NotificationTriggerTouchListener implements View.OnTouchListener {
1375         VelocityTracker mVT;
1376         float mInitialTouchX, mInitialTouchY;
1377         int mTouchSlop;
1378 
NotificationTriggerTouchListener()1379         public NotificationTriggerTouchListener() {
1380             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
1381         }
1382 
1383         private Runnable mHiliteOnR = new Runnable() { public void run() {
1384             mNotificationArea.setBackgroundResource(
1385                 com.android.internal.R.drawable.list_selector_pressed_holo_dark);
1386         }};
hilite(final boolean on)1387         public void hilite(final boolean on) {
1388             if (on) {
1389                 mNotificationArea.postDelayed(mHiliteOnR, 100);
1390             } else {
1391                 mNotificationArea.removeCallbacks(mHiliteOnR);
1392                 mNotificationArea.setBackgroundDrawable(null);
1393             }
1394         }
1395 
onTouch(View v, MotionEvent event)1396         public boolean onTouch(View v, MotionEvent event) {
1397 //            Slog.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)",
1398 //                        event.getX(),
1399 //                        event.getY(),
1400 //                        mInitialTouchX,
1401 //                        mInitialTouchY));
1402 
1403             if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
1404                 return true;
1405             }
1406 
1407             final int action = event.getAction();
1408             switch (action) {
1409                 case MotionEvent.ACTION_DOWN:
1410                     mVT = VelocityTracker.obtain();
1411                     mInitialTouchX = event.getX();
1412                     mInitialTouchY = event.getY();
1413                     hilite(true);
1414                     // fall through
1415                 case MotionEvent.ACTION_OUTSIDE:
1416                 case MotionEvent.ACTION_MOVE:
1417                     // check for fling
1418                     if (mVT != null) {
1419                         mVT.addMovement(event);
1420                         mVT.computeCurrentVelocity(1000); // pixels per second
1421                         // require a little more oomph once we're already in peekaboo mode
1422                         if (mVT.getYVelocity() < -mNotificationFlingVelocity) {
1423                             animateExpand();
1424                             visibilityChanged(true);
1425                             hilite(false);
1426                             mVT.recycle();
1427                             mVT = null;
1428                         }
1429                     }
1430                     return true;
1431                 case MotionEvent.ACTION_UP:
1432                 case MotionEvent.ACTION_CANCEL:
1433                     hilite(false);
1434                     if (mVT != null) {
1435                         if (action == MotionEvent.ACTION_UP
1436                          // was this a sloppy tap?
1437                          && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop
1438                          && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3)
1439                          // dragging off the bottom doesn't count
1440                          && (int)event.getY() < v.getBottom()) {
1441                             animateExpand();
1442                             visibilityChanged(true);
1443                             v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
1444                             v.playSoundEffect(SoundEffectConstants.CLICK);
1445                         }
1446 
1447                         mVT.recycle();
1448                         mVT = null;
1449                         return true;
1450                     }
1451             }
1452             return false;
1453         }
1454     }
1455 
resetNotificationPeekFadeTimer()1456     public void resetNotificationPeekFadeTimer() {
1457         if (DEBUG) {
1458             Slog.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY
1459                 + "ms from now");
1460         }
1461         mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
1462         mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK,
1463                 NOTIFICATION_PEEK_FADE_DELAY);
1464     }
1465 
1466     private class NotificationIconTouchListener implements View.OnTouchListener {
1467         VelocityTracker mVT;
1468         int mPeekIndex;
1469         float mInitialTouchX, mInitialTouchY;
1470         int mTouchSlop;
1471 
NotificationIconTouchListener()1472         public NotificationIconTouchListener() {
1473             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
1474         }
1475 
onTouch(View v, MotionEvent event)1476         public boolean onTouch(View v, MotionEvent event) {
1477             boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
1478             boolean panelShowing = mNotificationPanel.isShowing();
1479             if (panelShowing) return false;
1480 
1481             int numIcons = mIconLayout.getChildCount();
1482             int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth());
1483             if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1;
1484             else if (newPeekIndex < 0) newPeekIndex = 0;
1485 
1486             final int action = event.getAction();
1487             switch (action) {
1488                 case MotionEvent.ACTION_DOWN:
1489                     mVT = VelocityTracker.obtain();
1490                     mInitialTouchX = event.getX();
1491                     mInitialTouchY = event.getY();
1492                     mPeekIndex = -1;
1493 
1494                     // fall through
1495                 case MotionEvent.ACTION_OUTSIDE:
1496                 case MotionEvent.ACTION_MOVE:
1497                     // peek and switch icons if necessary
1498 
1499                     if (newPeekIndex != mPeekIndex) {
1500                         mPeekIndex = newPeekIndex;
1501 
1502                         if (DEBUG) Slog.d(TAG, "will peek at notification #" + mPeekIndex);
1503                         Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
1504                         peekMsg.arg1 = mPeekIndex;
1505 
1506                         mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1507 
1508                         if (peeking) {
1509                             // no delay if we're scrubbing left-right
1510                             mHandler.sendMessage(peekMsg);
1511                         } else {
1512                             // wait for fling
1513                             mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH);
1514                         }
1515                     }
1516 
1517                     // check for fling
1518                     if (mVT != null) {
1519                         mVT.addMovement(event);
1520                         mVT.computeCurrentVelocity(1000); // pixels per second
1521                         // require a little more oomph once we're already in peekaboo mode
1522                         if (!panelShowing && (
1523                                (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
1524                             || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
1525                             mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1526                             mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
1527                             mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1528                             mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
1529                         }
1530                     }
1531                     return true;
1532                 case MotionEvent.ACTION_UP:
1533                 case MotionEvent.ACTION_CANCEL:
1534                     mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1535                     if (!peeking) {
1536                         if (action == MotionEvent.ACTION_UP
1537                                 // was this a sloppy tap?
1538                                 && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop
1539                                 && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3)
1540                                 // dragging off the bottom doesn't count
1541                                 && (int)event.getY() < v.getBottom()) {
1542                             Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
1543                             peekMsg.arg1 = mPeekIndex;
1544                             mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1545                             mHandler.sendMessage(peekMsg);
1546 
1547                             v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
1548                             v.playSoundEffect(SoundEffectConstants.CLICK);
1549 
1550                             peeking = true; // not technically true yet, but the next line will run
1551                         }
1552                     }
1553 
1554                     if (peeking) {
1555                         resetNotificationPeekFadeTimer();
1556                     }
1557 
1558                     mVT.recycle();
1559                     mVT = null;
1560                     return true;
1561             }
1562             return false;
1563         }
1564     }
1565 
addNotificationViews(IBinder key, StatusBarNotification notification)1566     StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
1567         if (DEBUG) {
1568             Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
1569         }
1570         // Construct the icon.
1571         final StatusBarIconView iconView = new StatusBarIconView(mContext,
1572                 notification.pkg + "/0x" + Integer.toHexString(notification.id),
1573                 notification.notification);
1574         iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1575 
1576         final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
1577                     notification.notification.icon,
1578                     notification.notification.iconLevel,
1579                     notification.notification.number,
1580                     notification.notification.tickerText);
1581         if (!iconView.set(ic)) {
1582             handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
1583             return null;
1584         }
1585         // Construct the expanded view.
1586         NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
1587         if (!inflateViews(entry, mPile)) {
1588             handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
1589                     + notification);
1590             return null;
1591         }
1592 
1593         // Add the icon.
1594         int pos = mNotificationData.add(entry);
1595         if (DEBUG) {
1596             Slog.d(TAG, "addNotificationViews: added at " + pos);
1597         }
1598         updateNotificationIcons();
1599 
1600         return iconView;
1601     }
1602 
reloadAllNotificationIcons()1603     private void reloadAllNotificationIcons() {
1604         if (mIconLayout == null) return;
1605         mIconLayout.removeAllViews();
1606         updateNotificationIcons();
1607     }
1608 
updateNotificationIcons()1609     private void updateNotificationIcons() {
1610         // XXX: need to implement a new limited linear layout class
1611         // to avoid removing & readding everything
1612 
1613         if (mIconLayout == null) return;
1614 
1615         // first, populate the main notification panel
1616         loadNotificationPanel();
1617 
1618         final LinearLayout.LayoutParams params
1619             = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
1620 
1621         // alternate behavior in DND mode
1622         if (mNotificationDNDMode) {
1623             if (mIconLayout.getChildCount() == 0) {
1624                 final Notification dndNotification = new Notification.Builder(mContext)
1625                     .setContentTitle(mContext.getText(R.string.notifications_off_title))
1626                     .setContentText(mContext.getText(R.string.notifications_off_text))
1627                     .setSmallIcon(R.drawable.ic_notification_dnd)
1628                     .setOngoing(true)
1629                     .getNotification();
1630 
1631                 final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd",
1632                         dndNotification);
1633                 iconView.setImageResource(R.drawable.ic_notification_dnd);
1634                 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1635                 iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0);
1636 
1637                 mNotificationDNDDummyEntry = new NotificationData.Entry(
1638                         null,
1639                         new StatusBarNotification("", 0, "", 0, 0, dndNotification),
1640                         iconView);
1641 
1642                 mIconLayout.addView(iconView, params);
1643             }
1644 
1645             return;
1646         } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) {
1647             // if icons are disabled but we're not in DND mode, this is probably Setup and we should
1648             // just leave the area totally empty
1649             return;
1650         }
1651 
1652         int N = mNotificationData.size();
1653 
1654         if (DEBUG) {
1655             Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
1656         }
1657 
1658         ArrayList<View> toShow = new ArrayList<View>();
1659 
1660         // Extra Special Icons
1661         // The IME switcher and compatibility mode icons take the place of notifications. You didn't
1662         // need to see all those new emails, did you?
1663         int maxNotificationIconsCount = mMaxNotificationIcons;
1664         if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount --;
1665         if (mCompatModeButton.getVisibility()        != View.GONE) maxNotificationIconsCount --;
1666 
1667         for (int i=0; i< maxNotificationIconsCount; i++) {
1668             if (i>=N) break;
1669             toShow.add(mNotificationData.get(N-i-1).icon);
1670         }
1671 
1672         ArrayList<View> toRemove = new ArrayList<View>();
1673         for (int i=0; i<mIconLayout.getChildCount(); i++) {
1674             View child = mIconLayout.getChildAt(i);
1675             if (!toShow.contains(child)) {
1676                 toRemove.add(child);
1677             }
1678         }
1679 
1680         for (View remove : toRemove) {
1681             mIconLayout.removeView(remove);
1682         }
1683 
1684         for (int i=0; i<toShow.size(); i++) {
1685             View v = toShow.get(i);
1686             v.setPadding(mIconHPadding, 0, mIconHPadding, 0);
1687             if (v.getParent() == null) {
1688                 mIconLayout.addView(v, i, params);
1689             }
1690         }
1691     }
1692 
loadNotificationPanel()1693     private void loadNotificationPanel() {
1694         int N = mNotificationData.size();
1695 
1696         ArrayList<View> toShow = new ArrayList<View>();
1697 
1698         for (int i=0; i<N; i++) {
1699             View row = mNotificationData.get(N-i-1).row;
1700             toShow.add(row);
1701         }
1702 
1703         ArrayList<View> toRemove = new ArrayList<View>();
1704         for (int i=0; i<mPile.getChildCount(); i++) {
1705             View child = mPile.getChildAt(i);
1706             if (!toShow.contains(child)) {
1707                 toRemove.add(child);
1708             }
1709         }
1710 
1711         for (View remove : toRemove) {
1712             mPile.removeView(remove);
1713         }
1714 
1715         for (int i=0; i<toShow.size(); i++) {
1716             View v = toShow.get(i);
1717             if (v.getParent() == null) {
1718                 mPile.addView(v, N-1-i); // the notification panel has newest at the bottom
1719             }
1720         }
1721 
1722         mNotificationPanel.setNotificationCount(N);
1723     }
1724 
workAroundBadLayerDrawableOpacity(View v)1725     void workAroundBadLayerDrawableOpacity(View v) {
1726         LayerDrawable d = (LayerDrawable)v.getBackground();
1727         if (d == null) return;
1728         v.setBackgroundDrawable(null);
1729         d.setOpacity(PixelFormat.TRANSLUCENT);
1730         v.setBackgroundDrawable(d);
1731     }
1732 
inflateViews(NotificationData.Entry entry, ViewGroup parent)1733     private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
1734         StatusBarNotification sbn = entry.notification;
1735         RemoteViews remoteViews = sbn.notification.contentView;
1736         if (remoteViews == null) {
1737             return false;
1738         }
1739 
1740         // create the row view
1741         LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
1742                 Context.LAYOUT_INFLATER_SERVICE);
1743         View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
1744         workAroundBadLayerDrawableOpacity(row);
1745         View vetoButton = updateNotificationVetoButton(row, entry.notification);
1746         vetoButton.setContentDescription(mContext.getString(
1747                 R.string.accessibility_remove_notification));
1748 
1749         // the large icon
1750         ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
1751         if (sbn.notification.largeIcon != null) {
1752             largeIcon.setImageBitmap(sbn.notification.largeIcon);
1753             largeIcon.setContentDescription(sbn.notification.tickerText);
1754         } else {
1755             largeIcon.getLayoutParams().width = 0;
1756             largeIcon.setVisibility(View.INVISIBLE);
1757         }
1758         largeIcon.setContentDescription(sbn.notification.tickerText);
1759 
1760         // bind the click event to the content area
1761         ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
1762         // XXX: update to allow controls within notification views
1763         content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
1764 //        content.setOnFocusChangeListener(mFocusChangeListener);
1765         PendingIntent contentIntent = sbn.notification.contentIntent;
1766         if (contentIntent != null) {
1767             final View.OnClickListener listener = new NotificationClicker(
1768                     contentIntent, sbn.pkg, sbn.tag, sbn.id);
1769             largeIcon.setOnClickListener(listener);
1770             content.setOnClickListener(listener);
1771         } else {
1772             largeIcon.setOnClickListener(null);
1773             content.setOnClickListener(null);
1774         }
1775 
1776         View expanded = null;
1777         Exception exception = null;
1778         try {
1779             expanded = remoteViews.apply(mContext, content);
1780         }
1781         catch (RuntimeException e) {
1782             exception = e;
1783         }
1784         if (expanded == null) {
1785             final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
1786             Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
1787             return false;
1788         } else {
1789             content.addView(expanded);
1790             row.setDrawingCacheEnabled(true);
1791         }
1792 
1793         entry.row = row;
1794         entry.content = content;
1795         entry.expanded = expanded;
1796         entry.largeIcon = largeIcon;
1797 
1798         return true;
1799     }
1800 
clearAll()1801     public void clearAll() {
1802         try {
1803             mBarService.onClearAllNotifications();
1804         } catch (RemoteException ex) {
1805             // system process is dead if we're here.
1806         }
1807         animateCollapse();
1808         visibilityChanged(false);
1809     }
1810 
toggleRecentApps()1811     public void toggleRecentApps() {
1812         int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
1813                 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
1814         mHandler.removeMessages(msg);
1815         mHandler.sendEmptyMessage(msg);
1816     }
1817 
1818     public class TouchOutsideListener implements View.OnTouchListener {
1819         private int mMsg;
1820         private StatusBarPanel mPanel;
1821 
TouchOutsideListener(int msg, StatusBarPanel panel)1822         public TouchOutsideListener(int msg, StatusBarPanel panel) {
1823             mMsg = msg;
1824             mPanel = panel;
1825         }
1826 
onTouch(View v, MotionEvent ev)1827         public boolean onTouch(View v, MotionEvent ev) {
1828             final int action = ev.getAction();
1829             if (action == MotionEvent.ACTION_OUTSIDE
1830                     || (action == MotionEvent.ACTION_DOWN
1831                         && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
1832                 mHandler.removeMessages(mMsg);
1833                 mHandler.sendEmptyMessage(mMsg);
1834                 return true;
1835             }
1836             return false;
1837         }
1838     }
1839 
dump(FileDescriptor fd, PrintWriter pw, String[] args)1840     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1841         pw.print("mDisabled=0x");
1842         pw.println(Integer.toHexString(mDisabled));
1843         pw.println("mNetworkController:");
1844         mNetworkController.dump(fd, pw, args);
1845     }
1846 }
1847 
1848 
1849