TabletStatusBar.java revision 998bb765290066822ff8a3b378dadd71deb0ce73
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
17package com.android.systemui.statusbar.tablet;
18
19import android.animation.LayoutTransition;
20import android.animation.ObjectAnimator;
21import android.app.ActivityManager;
22import android.app.ActivityManagerNative;
23import android.app.Notification;
24import android.app.PendingIntent;
25import android.app.StatusBarManager;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.SharedPreferences;
31import android.content.res.Configuration;
32import android.content.res.Resources;
33import android.graphics.PixelFormat;
34import android.graphics.Point;
35import android.graphics.drawable.Drawable;
36import android.graphics.drawable.LayerDrawable;
37import android.inputmethodservice.InputMethodService;
38import android.os.IBinder;
39import android.os.Message;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.text.TextUtils;
43import android.util.Slog;
44import android.view.Display;
45import android.view.Gravity;
46import android.view.IWindowManager;
47import android.view.KeyEvent;
48import android.view.MotionEvent;
49import android.view.SoundEffectConstants;
50import android.view.VelocityTracker;
51import android.view.View;
52import android.view.ViewConfiguration;
53import android.view.ViewGroup;
54import android.view.ViewGroup.LayoutParams;
55import android.view.WindowManager;
56import android.view.WindowManagerImpl;
57import android.view.accessibility.AccessibilityEvent;
58import android.widget.ImageView;
59import android.widget.LinearLayout;
60import android.widget.RemoteViews;
61import android.widget.ScrollView;
62import android.widget.TextView;
63
64import com.android.internal.statusbar.StatusBarIcon;
65import com.android.internal.statusbar.StatusBarNotification;
66import com.android.systemui.R;
67import com.android.systemui.recent.RecentTasksLoader;
68import com.android.systemui.recent.RecentsPanelView;
69import com.android.systemui.statusbar.BaseStatusBar;
70import com.android.systemui.statusbar.DoNotDisturb;
71import com.android.systemui.statusbar.NotificationData;
72import com.android.systemui.statusbar.SignalClusterView;
73import com.android.systemui.statusbar.StatusBarIconView;
74import com.android.systemui.statusbar.NotificationData.Entry;
75import com.android.systemui.statusbar.policy.BatteryController;
76import com.android.systemui.statusbar.policy.BluetoothController;
77import com.android.systemui.statusbar.policy.CompatModeButton;
78import com.android.systemui.statusbar.policy.LocationController;
79import com.android.systemui.statusbar.policy.NetworkController;
80import com.android.systemui.statusbar.policy.NotificationRowLayout;
81import com.android.systemui.statusbar.policy.Prefs;
82
83import java.io.FileDescriptor;
84import java.io.PrintWriter;
85import java.util.ArrayList;
86
87public class TabletStatusBar extends BaseStatusBar implements
88        InputMethodsPanel.OnHardKeyboardEnabledChangeListener,
89        RecentsPanelView.OnRecentsPanelVisibilityChangedListener {
90    public static final boolean DEBUG = false;
91    public static final boolean DEBUG_COMPAT_HELP = false;
92    public static final String TAG = "TabletStatusBar";
93
94
95    public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
96    public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
97    public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002;
98    public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003;
99    // 1020-1029 reserved for BaseStatusBar
100    public static final int MSG_SHOW_CHROME = 1030;
101    public static final int MSG_HIDE_CHROME = 1031;
102    public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040;
103    public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041;
104    public static final int MSG_OPEN_COMPAT_MODE_PANEL = 1050;
105    public static final int MSG_CLOSE_COMPAT_MODE_PANEL = 1051;
106    public static final int MSG_STOP_TICKER = 2000;
107
108    // Fitts' Law assistance for LatinIME; see policy.EventHole
109    private static final boolean FAKE_SPACE_BAR = true;
110
111    // Notification "peeking" (flyover preview of individual notifications)
112    final static int NOTIFICATION_PEEK_HOLD_THRESH = 200; // ms
113    final static int NOTIFICATION_PEEK_FADE_DELAY = 3000; // ms
114
115    private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
116    private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
117
118    // The height of the bar, as definied by the build.  It may be taller if we're plugged
119    // into hdmi.
120    int mNaturalBarHeight = -1;
121    int mIconSize = -1;
122    int mIconHPadding = -1;
123    int mNavIconWidth = -1;
124    int mMenuNavIconWidth = -1;
125    private int mMaxNotificationIcons = 5;
126
127    IWindowManager mWindowManager;
128
129    TabletStatusBarView mStatusBarView;
130    View mNotificationArea;
131    View mNotificationTrigger;
132    NotificationIconArea mNotificationIconArea;
133    ViewGroup mNavigationArea;
134
135    boolean mNotificationDNDMode;
136    NotificationData.Entry mNotificationDNDDummyEntry;
137
138    ImageView mBackButton;
139    View mHomeButton;
140    View mMenuButton;
141    View mRecentButton;
142    private boolean mAltBackButtonEnabledForIme;
143
144    ViewGroup mFeedbackIconArea; // notification icons, IME icon, compat icon
145    InputMethodButton mInputMethodSwitchButton;
146    CompatModeButton mCompatModeButton;
147
148    NotificationPanel mNotificationPanel;
149    WindowManager.LayoutParams mNotificationPanelParams;
150    NotificationPeekPanel mNotificationPeekWindow;
151    ViewGroup mNotificationPeekRow;
152    int mNotificationPeekIndex;
153    IBinder mNotificationPeekKey;
154    LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight;
155
156    int mNotificationPeekTapDuration;
157    int mNotificationFlingVelocity;
158
159    BatteryController mBatteryController;
160    BluetoothController mBluetoothController;
161    LocationController mLocationController;
162    NetworkController mNetworkController;
163    DoNotDisturb mDoNotDisturb;
164
165    ViewGroup mBarContents;
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 InputMethodsPanel mInputMethodsPanel;
183    private CompatModePanel mCompatModePanel;
184
185    private int mSystemUiVisibility = 0;
186
187    private int mNavigationIconHints = 0;
188
189    public Context getContext() { return mContext; }
190
191    private View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() {
192        public boolean onTouch(View v, MotionEvent event) {
193            switch(event.getAction()) {
194                case MotionEvent.ACTION_DOWN:
195                    showSearchPanel();
196                break;
197            }
198            return false;
199        }
200    };
201
202    @Override
203    protected void createAndAddWindows() {
204        addStatusBarWindow();
205        addPanelWindows();
206    }
207
208    private void addStatusBarWindow() {
209        final View sb = makeStatusBarView();
210
211        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
212                ViewGroup.LayoutParams.MATCH_PARENT,
213                ViewGroup.LayoutParams.MATCH_PARENT,
214                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
215                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
216                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
217                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
218                // We use a pixel format of RGB565 for the status bar to save memory bandwidth and
219                // to ensure that the layer can be handled by HWComposer.  On some devices the
220                // HWComposer is unable to handle SW-rendered RGBX_8888 layers.
221                PixelFormat.RGB_565);
222
223        // We explicitly leave FLAG_HARDWARE_ACCELERATED out of the flags.  The status bar occupies
224        // very little screen real-estate and is updated fairly frequently.  By using CPU rendering
225        // for the status bar, we prevent the GPU from having to wake up just to do these small
226        // updates, which should help keep power consumption down.
227
228        lp.gravity = getStatusBarGravity();
229        lp.setTitle("SystemBar");
230        lp.packageName = mContext.getPackageName();
231        WindowManagerImpl.getDefault().addView(sb, lp);
232    }
233
234    protected void addPanelWindows() {
235        final Context context = mContext;
236        final Resources res = mContext.getResources();
237
238        // Notification Panel
239        mNotificationPanel = (NotificationPanel)View.inflate(context,
240                R.layout.system_bar_notification_panel, null);
241        mNotificationPanel.setBar(this);
242        mNotificationPanel.show(false, false);
243        mNotificationPanel.setOnTouchListener(
244                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel));
245
246        // the battery icon
247        mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery));
248        mBatteryController.addLabelView(
249                (TextView)mNotificationPanel.findViewById(R.id.battery_text));
250
251        // Bt
252        mBluetoothController.addIconView(
253                (ImageView)mNotificationPanel.findViewById(R.id.bluetooth));
254
255        // network icons: either a combo icon that switches between mobile and data, or distinct
256        // mobile and data icons
257        final ImageView mobileRSSI =
258                (ImageView)mNotificationPanel.findViewById(R.id.mobile_signal);
259        if (mobileRSSI != null) {
260            mNetworkController.addPhoneSignalIconView(mobileRSSI);
261        }
262        final ImageView wifiRSSI =
263                (ImageView)mNotificationPanel.findViewById(R.id.wifi_signal);
264        if (wifiRSSI != null) {
265            mNetworkController.addWifiIconView(wifiRSSI);
266        }
267        mNetworkController.addWifiLabelView(
268                (TextView)mNotificationPanel.findViewById(R.id.wifi_text));
269
270        mNetworkController.addDataTypeIconView(
271                (ImageView)mNotificationPanel.findViewById(R.id.mobile_type));
272        mNetworkController.addMobileLabelView(
273                (TextView)mNotificationPanel.findViewById(R.id.mobile_text));
274        mNetworkController.addCombinedLabelView(
275                (TextView)mBarContents.findViewById(R.id.network_text));
276
277        mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
278
279        WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams(
280                res.getDimensionPixelSize(R.dimen.notification_panel_width),
281                getNotificationPanelHeight(),
282                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
283                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
284                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
285                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
286                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
287                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
288                PixelFormat.TRANSLUCENT);
289        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
290        lp.setTitle("NotificationPanel");
291        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
292                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
293        lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation
294//        lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
295
296        WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
297
298        // Recents Panel
299        mRecentTasksLoader = new RecentTasksLoader(context);
300        updateRecentsPanel();
301
302        // Search Panel
303        mStatusBarView.setBar(this);
304        mHomeButton.setOnTouchListener(mHomeSearchActionListener);
305        updateSearchPanel();
306
307        // Input methods Panel
308        mInputMethodsPanel = (InputMethodsPanel) View.inflate(context,
309                R.layout.system_bar_input_methods_panel, null);
310        mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this);
311        mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener(
312                MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel));
313        mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton);
314        mStatusBarView.setIgnoreChildren(2, mInputMethodSwitchButton, mInputMethodsPanel);
315        lp = new WindowManager.LayoutParams(
316                ViewGroup.LayoutParams.WRAP_CONTENT,
317                ViewGroup.LayoutParams.WRAP_CONTENT,
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.RIGHT;
325        lp.setTitle("InputMethodsPanel");
326        lp.windowAnimations = R.style.Animation_RecentPanel;
327
328        WindowManagerImpl.getDefault().addView(mInputMethodsPanel, lp);
329
330        // Compatibility mode selector panel
331        mCompatModePanel = (CompatModePanel) View.inflate(context,
332                R.layout.system_bar_compat_mode_panel, null);
333        mCompatModePanel.setOnTouchListener(new TouchOutsideListener(
334                MSG_CLOSE_COMPAT_MODE_PANEL, mCompatModePanel));
335        mCompatModePanel.setTrigger(mCompatModeButton);
336        mCompatModePanel.setVisibility(View.GONE);
337        mStatusBarView.setIgnoreChildren(3, mCompatModeButton, mCompatModePanel);
338        lp = new WindowManager.LayoutParams(
339                250,
340                ViewGroup.LayoutParams.WRAP_CONTENT,
341                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
342                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
343                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
344                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
345                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
346                PixelFormat.TRANSLUCENT);
347        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
348        lp.setTitle("CompatModePanel");
349        lp.windowAnimations = android.R.style.Animation_Dialog;
350
351        WindowManagerImpl.getDefault().addView(mCompatModePanel, lp);
352
353        mRecentButton.setOnTouchListener(mRecentsPanel);
354
355        mPile = (NotificationRowLayout)mNotificationPanel.findViewById(R.id.content);
356        mPile.removeAllViews();
357        mPile.setLongPressListener(getNotificationLongClicker());
358
359        ScrollView scroller = (ScrollView)mPile.getParent();
360        scroller.setFillViewport(true);
361    }
362
363    @Override
364    protected int getExpandedViewMaxHeight() {
365        return getNotificationPanelHeight();
366    }
367
368    private int getNotificationPanelHeight() {
369        final Resources res = mContext.getResources();
370        final Display d = WindowManagerImpl.getDefault().getDefaultDisplay();
371        final Point size = new Point();
372        d.getRealSize(size);
373        return Math.max(res.getDimensionPixelSize(R.dimen.notification_panel_min_height), size.y);
374    }
375
376    @Override
377    public void start() {
378        super.start(); // will add the main bar view
379    }
380
381    @Override
382    protected void onConfigurationChanged(Configuration newConfig) {
383        loadDimens();
384        mNotificationPanelParams.height = getNotificationPanelHeight();
385        WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel,
386                mNotificationPanelParams);
387        mRecentsPanel.updateValuesFromResources();
388    }
389
390    protected void loadDimens() {
391        final Resources res = mContext.getResources();
392
393        mNaturalBarHeight = res.getDimensionPixelSize(
394                com.android.internal.R.dimen.navigation_bar_height);
395
396        int newIconSize = res.getDimensionPixelSize(
397            com.android.internal.R.dimen.system_bar_icon_size);
398        int newIconHPadding = res.getDimensionPixelSize(
399            R.dimen.status_bar_icon_padding);
400        int newNavIconWidth = res.getDimensionPixelSize(R.dimen.navigation_key_width);
401        int newMenuNavIconWidth = res.getDimensionPixelSize(R.dimen.navigation_menu_key_width);
402
403        if (mNavigationArea != null && newNavIconWidth != mNavIconWidth) {
404            mNavIconWidth = newNavIconWidth;
405
406            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
407                     mNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT);
408            mBackButton.setLayoutParams(lp);
409            mHomeButton.setLayoutParams(lp);
410            mRecentButton.setLayoutParams(lp);
411        }
412
413        if (mNavigationArea != null && newMenuNavIconWidth != mMenuNavIconWidth) {
414            mMenuNavIconWidth = newMenuNavIconWidth;
415
416            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
417                     mMenuNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT);
418            mMenuButton.setLayoutParams(lp);
419        }
420
421        if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) {
422//            Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding);
423            mIconHPadding = newIconHPadding;
424            mIconSize = newIconSize;
425            reloadAllNotificationIcons(); // reload the tray
426        }
427
428        final int numIcons = res.getInteger(R.integer.config_maxNotificationIcons);
429        if (numIcons != mMaxNotificationIcons) {
430            mMaxNotificationIcons = numIcons;
431            if (DEBUG) Slog.d(TAG, "max notification icons: " + mMaxNotificationIcons);
432            reloadAllNotificationIcons();
433        }
434    }
435
436    public View getStatusBarView() {
437        return mStatusBarView;
438    }
439
440    protected View makeStatusBarView() {
441        final Context context = mContext;
442
443        mWindowManager = IWindowManager.Stub.asInterface(
444                ServiceManager.getService(Context.WINDOW_SERVICE));
445
446        loadDimens();
447
448        final TabletStatusBarView sb = (TabletStatusBarView)View.inflate(
449                context, R.layout.system_bar, null);
450        mStatusBarView = sb;
451
452        sb.setHandler(mHandler);
453
454        try {
455            // Sanity-check that someone hasn't set up the config wrong and asked for a navigation
456            // bar on a tablet that has only the system bar
457            if (mWindowManager.hasNavigationBar()) {
458                Slog.e(TAG, "Tablet device cannot show navigation bar and system bar");
459            }
460        } catch (RemoteException ex) {
461        }
462
463        mBarContents = (ViewGroup) sb.findViewById(R.id.bar_contents);
464
465        // the whole right-hand side of the bar
466        mNotificationArea = sb.findViewById(R.id.notificationArea);
467        mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener());
468
469        // the button to open the notification area
470        mNotificationTrigger = sb.findViewById(R.id.notificationTrigger);
471
472        // the more notifications icon
473        mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
474
475        // where the icons go
476        mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
477
478        ViewConfiguration vc = ViewConfiguration.get(context);
479        mNotificationPeekTapDuration = vc.getTapTimeout();
480        mNotificationFlingVelocity = 300; // px/s
481
482        mTicker = new TabletTicker(this);
483
484        // The icons
485        mLocationController = new LocationController(mContext); // will post a notification
486
487        // watch the PREF_DO_NOT_DISTURB and convert to appropriate disable() calls
488        mDoNotDisturb = new DoNotDisturb(mContext);
489
490        mBatteryController = new BatteryController(mContext);
491        mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
492        mBluetoothController = new BluetoothController(mContext);
493        mBluetoothController.addIconView((ImageView)sb.findViewById(R.id.bluetooth));
494
495        mNetworkController = new NetworkController(mContext);
496        final SignalClusterView signalCluster =
497                (SignalClusterView)sb.findViewById(R.id.signal_cluster);
498        mNetworkController.addSignalCluster(signalCluster);
499
500        // The navigation buttons
501        mBackButton = (ImageView)sb.findViewById(R.id.back);
502        mNavigationArea = (ViewGroup) sb.findViewById(R.id.navigationArea);
503        mHomeButton = mNavigationArea.findViewById(R.id.home);
504        mMenuButton = mNavigationArea.findViewById(R.id.menu);
505        mRecentButton = mNavigationArea.findViewById(R.id.recent_apps);
506        mRecentButton.setOnClickListener(mOnClickListener);
507
508        LayoutTransition lt = new LayoutTransition();
509        lt.setDuration(250);
510        // don't wait for these transitions; we just want icons to fade in/out, not move around
511        lt.setDuration(LayoutTransition.CHANGE_APPEARING, 0);
512        lt.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 0);
513        lt.addTransitionListener(new LayoutTransition.TransitionListener() {
514            public void endTransition(LayoutTransition transition, ViewGroup container,
515                    View view, int transitionType) {
516                // ensure the menu button doesn't stick around on the status bar after it's been
517                // removed
518                mBarContents.invalidate();
519            }
520            public void startTransition(LayoutTransition transition, ViewGroup container,
521                    View view, int transitionType) {}
522        });
523        mNavigationArea.setLayoutTransition(lt);
524        // no multi-touch on the nav buttons
525        mNavigationArea.setMotionEventSplittingEnabled(false);
526
527        // The bar contents buttons
528        mFeedbackIconArea = (ViewGroup)sb.findViewById(R.id.feedbackIconArea);
529        mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton);
530        // Overwrite the lister
531        mInputMethodSwitchButton.setOnClickListener(mOnClickListener);
532
533        mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton);
534        mCompatModeButton.setOnClickListener(mOnClickListener);
535        mCompatModeButton.setVisibility(View.GONE);
536
537        // for redirecting errant bar taps to the IME
538        mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar);
539
540        // "shadows" of the status bar features, for lights-out mode
541        mShadow = sb.findViewById(R.id.bar_shadow);
542        mShadow.setOnTouchListener(
543            new View.OnTouchListener() {
544                public boolean onTouch(View v, MotionEvent ev) {
545                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
546                        // even though setting the systemUI visibility below will turn these views
547                        // on, we need them to come up faster so that they can catch this motion
548                        // event
549                        mShadow.setVisibility(View.GONE);
550                        mBarContents.setVisibility(View.VISIBLE);
551
552                        try {
553                            mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE);
554                        } catch (RemoteException ex) {
555                            // system process dead
556                        }
557                    }
558                    return false;
559                }
560            });
561
562        // tuning parameters
563        final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 750;
564        final int LIGHTS_GOING_OUT_SHADOW_DURATION = 750;
565        final int LIGHTS_GOING_OUT_SHADOW_DELAY    = 0;
566
567        final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200;
568//        final int LIGHTS_COMING_UP_SYSBAR_DELAY    = 50;
569        final int LIGHTS_COMING_UP_SHADOW_DURATION = 0;
570
571        LayoutTransition xition = new LayoutTransition();
572        xition.setAnimator(LayoutTransition.APPEARING,
573               ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f));
574        xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION);
575        xition.setStartDelay(LayoutTransition.APPEARING, 0);
576        xition.setAnimator(LayoutTransition.DISAPPEARING,
577               ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
578        xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION);
579        xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
580        ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition);
581
582        xition = new LayoutTransition();
583        xition.setAnimator(LayoutTransition.APPEARING,
584               ObjectAnimator.ofFloat(null, "alpha", 0f, 1f));
585        xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION);
586        xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY);
587        xition.setAnimator(LayoutTransition.DISAPPEARING,
588               ObjectAnimator.ofFloat(null, "alpha", 1f, 0f));
589        xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION);
590        xition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
591        ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition);
592
593        // set the initial view visibility
594        setAreThereNotifications();
595
596        // receive broadcasts
597        IntentFilter filter = new IntentFilter();
598        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
599        filter.addAction(Intent.ACTION_SCREEN_OFF);
600        context.registerReceiver(mBroadcastReceiver, filter);
601
602        return sb;
603    }
604
605    @Override
606    protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) {
607        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
608                (int) mContext.getResources().getDimension(R.dimen.status_bar_recents_width),
609                ViewGroup.LayoutParams.MATCH_PARENT,
610                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
611                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
612                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
613                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
614                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
615                PixelFormat.TRANSLUCENT);
616        lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
617        lp.setTitle("RecentsPanel");
618        lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
619        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
620            | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
621
622        return lp;
623    }
624
625    @Override
626    protected WindowManager.LayoutParams getSearchLayoutParams(LayoutParams layoutParams) {
627        boolean opaque = false;
628        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
629                LayoutParams.MATCH_PARENT,
630                LayoutParams.MATCH_PARENT,
631                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
632                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
633                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
634                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
635                (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT));
636        if (ActivityManager.isHighEndGfx(mDisplay)) {
637            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
638        } else {
639            lp.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
640            lp.dimAmount = 0.7f;
641        }
642        lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
643        lp.setTitle("SearchPanel");
644        // TODO: Define custom animation for Search panel
645        lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
646        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
647                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
648        return lp;
649    }
650
651    protected void updateRecentsPanel() {
652        super.updateRecentsPanel(R.layout.system_bar_recent_panel);
653        mRecentsPanel.setStatusBarView(mStatusBarView);
654    }
655
656    @Override
657    protected void updateSearchPanel() {
658        super.updateSearchPanel();
659        mSearchPanelView.setStatusBarView(mStatusBarView);
660        mStatusBarView.setDelegateView(mSearchPanelView);
661    }
662
663    @Override
664    public void showSearchPanel() {
665        super.showSearchPanel();
666        WindowManager.LayoutParams lp =
667            (android.view.WindowManager.LayoutParams) mStatusBarView.getLayoutParams();
668        lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
669        WindowManagerImpl.getDefault().updateViewLayout(mStatusBarView, lp);
670    }
671
672    @Override
673    public void hideSearchPanel() {
674        super.hideSearchPanel();
675        WindowManager.LayoutParams lp =
676            (android.view.WindowManager.LayoutParams) mStatusBarView.getLayoutParams();
677        lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
678        WindowManagerImpl.getDefault().updateViewLayout(mStatusBarView, lp);
679    }
680
681    public int getStatusBarHeight() {
682        return mStatusBarView != null ? mStatusBarView.getHeight()
683                : mContext.getResources().getDimensionPixelSize(
684                        com.android.internal.R.dimen.navigation_bar_height);
685    }
686
687    protected int getStatusBarGravity() {
688        return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
689    }
690
691    public void onBarHeightChanged(int height) {
692        final WindowManager.LayoutParams lp
693                = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams();
694        if (lp == null) {
695            // haven't been added yet
696            return;
697        }
698        if (lp.height != height) {
699            lp.height = height;
700            final WindowManager wm = WindowManagerImpl.getDefault();
701            wm.updateViewLayout(mStatusBarView, lp);
702        }
703    }
704
705    @Override
706    protected BaseStatusBar.H createHandler() {
707        return new TabletStatusBar.H();
708    }
709
710    private class H extends BaseStatusBar.H {
711        public void handleMessage(Message m) {
712            super.handleMessage(m);
713            switch (m.what) {
714                case MSG_OPEN_NOTIFICATION_PEEK:
715                    if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1);
716
717                    if (m.arg1 >= 0) {
718                        final int N = mNotificationData.size();
719
720                        if (!mNotificationDNDMode) {
721                            if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
722                                NotificationData.Entry entry = mNotificationData.get(N-1-mNotificationPeekIndex);
723                                entry.icon.setBackgroundColor(0);
724                                mNotificationPeekIndex = -1;
725                                mNotificationPeekKey = null;
726                            }
727                        }
728
729                        final int peekIndex = m.arg1;
730                        if (peekIndex < N) {
731                            //Slog.d(TAG, "loading peek: " + peekIndex);
732                            NotificationData.Entry entry =
733                                mNotificationDNDMode
734                                    ? mNotificationDNDDummyEntry
735                                    : mNotificationData.get(N-1-peekIndex);
736                            NotificationData.Entry copy = new NotificationData.Entry(
737                                    entry.key,
738                                    entry.notification,
739                                    entry.icon);
740                            inflateViews(copy, mNotificationPeekRow);
741
742                            if (mNotificationDNDMode) {
743                                copy.content.setOnClickListener(new View.OnClickListener() {
744                                    public void onClick(View v) {
745                                        SharedPreferences.Editor editor = Prefs.edit(mContext);
746                                        editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false);
747                                        editor.apply();
748                                        animateCollapse();
749                                        visibilityChanged(false);
750                                    }
751                                });
752                            }
753
754                            entry.icon.setBackgroundColor(0x20FFFFFF);
755
756//                          mNotificationPeekRow.setLayoutTransition(
757//                              peekIndex < mNotificationPeekIndex
758//                                  ? mNotificationPeekScrubLeft
759//                                  : mNotificationPeekScrubRight);
760
761                            mNotificationPeekRow.removeAllViews();
762                            mNotificationPeekRow.addView(copy.row);
763
764                            mNotificationPeekWindow.setVisibility(View.VISIBLE);
765                            mNotificationPanel.show(false, true);
766
767                            mNotificationPeekIndex = peekIndex;
768                            mNotificationPeekKey = entry.key;
769                        }
770                    }
771                    break;
772                case MSG_CLOSE_NOTIFICATION_PEEK:
773                    if (DEBUG) Slog.d(TAG, "closing notification peek window");
774                    mNotificationPeekWindow.setVisibility(View.GONE);
775                    mNotificationPeekRow.removeAllViews();
776
777                    final int N = mNotificationData.size();
778                    if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) {
779                        NotificationData.Entry entry =
780                            mNotificationDNDMode
781                                ? mNotificationDNDDummyEntry
782                                : mNotificationData.get(N-1-mNotificationPeekIndex);
783                        entry.icon.setBackgroundColor(0);
784                    }
785
786                    mNotificationPeekIndex = -1;
787                    mNotificationPeekKey = null;
788                    break;
789                case MSG_OPEN_NOTIFICATION_PANEL:
790                    if (DEBUG) Slog.d(TAG, "opening notifications panel");
791                    if (!mNotificationPanel.isShowing()) {
792                        mNotificationPanel.show(true, true);
793                        mNotificationArea.setVisibility(View.INVISIBLE);
794                        mTicker.halt();
795                    }
796                    break;
797                case MSG_CLOSE_NOTIFICATION_PANEL:
798                    if (DEBUG) Slog.d(TAG, "closing notifications panel");
799                    if (mNotificationPanel.isShowing()) {
800                        mNotificationPanel.show(false, true);
801                        mNotificationArea.setVisibility(View.VISIBLE);
802                    }
803                    break;
804                case MSG_OPEN_INPUT_METHODS_PANEL:
805                    if (DEBUG) Slog.d(TAG, "opening input methods panel");
806                    if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel();
807                    break;
808                case MSG_CLOSE_INPUT_METHODS_PANEL:
809                    if (DEBUG) Slog.d(TAG, "closing input methods panel");
810                    if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false);
811                    break;
812                case MSG_OPEN_COMPAT_MODE_PANEL:
813                    if (DEBUG) Slog.d(TAG, "opening compat panel");
814                    if (mCompatModePanel != null) mCompatModePanel.openPanel();
815                    break;
816                case MSG_CLOSE_COMPAT_MODE_PANEL:
817                    if (DEBUG) Slog.d(TAG, "closing compat panel");
818                    if (mCompatModePanel != null) mCompatModePanel.closePanel();
819                    break;
820                case MSG_SHOW_CHROME:
821                    if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)");
822                    mBarContents.setVisibility(View.VISIBLE);
823                    mShadow.setVisibility(View.GONE);
824                    mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
825                    notifyUiVisibilityChanged();
826                    break;
827                case MSG_HIDE_CHROME:
828                    if (DEBUG) Slog.d(TAG, "showing shadows (lights out)");
829                    animateCollapse();
830                    visibilityChanged(false);
831                    mBarContents.setVisibility(View.GONE);
832                    mShadow.setVisibility(View.VISIBLE);
833                    mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
834                    notifyUiVisibilityChanged();
835                    break;
836                case MSG_STOP_TICKER:
837                    mTicker.halt();
838                    break;
839            }
840        }
841    }
842
843    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
844        if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon);
845    }
846
847    public void updateIcon(String slot, int index, int viewIndex,
848            StatusBarIcon old, StatusBarIcon icon) {
849        if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon);
850    }
851
852    public void removeIcon(String slot, int index, int viewIndex) {
853        if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")");
854    }
855
856    public void addNotification(IBinder key, StatusBarNotification notification) {
857        if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
858        addNotificationViews(key, notification);
859
860        final boolean immersive = isImmersive();
861        if (false && immersive) {
862            // TODO: immersive mode popups for tablet
863        } else if (notification.notification.fullScreenIntent != null) {
864            // not immersive & a full-screen alert should be shown
865            Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;"
866                    + " sending fullScreenIntent");
867            try {
868                notification.notification.fullScreenIntent.send();
869            } catch (PendingIntent.CanceledException e) {
870            }
871        } else {
872            tick(key, notification, true);
873        }
874
875        setAreThereNotifications();
876    }
877
878    public void removeNotification(IBinder key) {
879        if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ")");
880        removeNotificationViews(key);
881        mTicker.remove(key);
882        setAreThereNotifications();
883    }
884
885    public void showClock(boolean show) {
886        View clock = mBarContents.findViewById(R.id.clock);
887        View network_text = mBarContents.findViewById(R.id.network_text);
888        if (clock != null) {
889            clock.setVisibility(show ? View.VISIBLE : View.GONE);
890        }
891        if (network_text != null) {
892            network_text.setVisibility((!show) ? View.VISIBLE : View.GONE);
893        }
894    }
895
896    public void disable(int state) {
897        int old = mDisabled;
898        int diff = state ^ old;
899        mDisabled = state;
900
901        // act accordingly
902        if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
903            boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
904            Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes"));
905            showClock(show);
906        }
907        if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
908            boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0;
909            Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes"));
910            mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE);
911        }
912        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
913            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
914                Slog.i(TAG, "DISABLE_EXPAND: yes");
915                animateCollapse();
916                visibilityChanged(false);
917            }
918        }
919        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
920            mNotificationDNDMode = Prefs.read(mContext)
921                        .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT);
922
923            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
924                Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":""));
925                mTicker.halt();
926            } else {
927                Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":""));
928            }
929
930            // refresh icons to show either notifications or the DND message
931            reloadAllNotificationIcons();
932        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
933            if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
934                mTicker.halt();
935            }
936        }
937        if ((diff & (StatusBarManager.DISABLE_RECENT
938                        | StatusBarManager.DISABLE_BACK
939                        | StatusBarManager.DISABLE_HOME)) != 0) {
940            setNavigationVisibility(state);
941
942            if ((state & StatusBarManager.DISABLE_RECENT) != 0) {
943                // close recents if it's visible
944                mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
945                mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
946            }
947        }
948    }
949
950    private void setNavigationVisibility(int visibility) {
951        boolean disableHome = ((visibility & StatusBarManager.DISABLE_HOME) != 0);
952        boolean disableRecent = ((visibility & StatusBarManager.DISABLE_RECENT) != 0);
953        boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0);
954
955        mBackButton.setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
956        mHomeButton.setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
957        mRecentButton.setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
958
959        mInputMethodSwitchButton.setScreenLocked(
960                (visibility & StatusBarManager.DISABLE_SYSTEM_INFO) != 0);
961    }
962
963    private boolean hasTicker(Notification n) {
964        return n.tickerView != null || !TextUtils.isEmpty(n.tickerText);
965    }
966
967    @Override
968    protected void tick(IBinder key, StatusBarNotification n, boolean firstTime) {
969        // Don't show the ticker when the windowshade is open.
970        if (mNotificationPanel.isShowing()) {
971            return;
972        }
973        // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification
974        // if it's a new notification.
975        if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) {
976            return;
977        }
978        // Show the ticker if one is requested. Also don't do this
979        // until status bar window is attached to the window manager,
980        // because...  well, what's the point otherwise?  And trying to
981        // run a ticker without being attached will crash!
982        if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
983            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
984                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
985                mTicker.add(key, n);
986                mFeedbackIconArea.setVisibility(View.GONE);
987            }
988        }
989    }
990
991    // called by TabletTicker when it's done with all queued ticks
992    public void doneTicking() {
993        mFeedbackIconArea.setVisibility(View.VISIBLE);
994    }
995
996    public void animateExpand() {
997        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
998        mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
999    }
1000
1001    public void animateCollapse() {
1002        animateCollapse(false);
1003    }
1004
1005    private void animateCollapse(boolean excludeRecents) {
1006        mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
1007        mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
1008        if (!excludeRecents) {
1009            mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
1010            mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
1011        }
1012        mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL);
1013        mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL);
1014        mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL);
1015        mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL);
1016    }
1017
1018    @Override // CommandQueue
1019    public void setNavigationIconHints(int hints) {
1020        if (hints == mNavigationIconHints) return;
1021
1022        if (DEBUG) {
1023            android.widget.Toast.makeText(mContext,
1024                "Navigation icon hints = " + hints,
1025                500).show();
1026        }
1027
1028        mNavigationIconHints = hints;
1029
1030        mBackButton.setAlpha(
1031            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f);
1032        mHomeButton.setAlpha(
1033            (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f);
1034        mRecentButton.setAlpha(
1035            (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f);
1036
1037        mBackButton.setImageResource(
1038            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT))
1039                ? R.drawable.ic_sysbar_back_ime
1040                : R.drawable.ic_sysbar_back);
1041    }
1042
1043    private void notifyUiVisibilityChanged() {
1044        try {
1045            mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility);
1046        } catch (RemoteException ex) {
1047        }
1048    }
1049
1050    @Override // CommandQueue
1051    public void setSystemUiVisibility(int vis, int mask) {
1052        final int oldVal = mSystemUiVisibility;
1053        final int newVal = (oldVal&~mask) | (vis&mask);
1054        final int diff = newVal ^ oldVal;
1055
1056        if (diff != 0) {
1057            mSystemUiVisibility = newVal;
1058
1059            if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) {
1060                mHandler.removeMessages(MSG_HIDE_CHROME);
1061                mHandler.removeMessages(MSG_SHOW_CHROME);
1062                mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE)
1063                        ? MSG_SHOW_CHROME : MSG_HIDE_CHROME);
1064            }
1065
1066            notifyUiVisibilityChanged();
1067        }
1068    }
1069
1070    public void setLightsOn(boolean on) {
1071        // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app
1072        // that can't handle lights-out mode.
1073        if (mMenuButton.getVisibility() == View.VISIBLE) {
1074            on = true;
1075        }
1076
1077        Slog.v(TAG, "setLightsOn(" + on + ")");
1078        if (on) {
1079            setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE);
1080        } else {
1081            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, View.SYSTEM_UI_FLAG_LOW_PROFILE);
1082        }
1083    }
1084
1085    public void topAppWindowChanged(boolean showMenu) {
1086        if (DEBUG) {
1087            Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button");
1088        }
1089        mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE);
1090
1091        // See above re: lights-out policy for legacy apps.
1092        if (showMenu) setLightsOn(true);
1093
1094        mCompatModeButton.refresh();
1095        if (mCompatModeButton.getVisibility() == View.VISIBLE) {
1096            if (DEBUG_COMPAT_HELP
1097                    || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) {
1098                showCompatibilityHelp();
1099            }
1100        } else {
1101            hideCompatibilityHelp();
1102            mCompatModePanel.closePanel();
1103        }
1104    }
1105
1106    private void showCompatibilityHelp() {
1107        if (mCompatibilityHelpDialog != null) {
1108            return;
1109        }
1110
1111        mCompatibilityHelpDialog = View.inflate(mContext, R.layout.compat_mode_help, null);
1112        View button = mCompatibilityHelpDialog.findViewById(R.id.button);
1113
1114        button.setOnClickListener(new View.OnClickListener() {
1115            @Override
1116            public void onClick(View v) {
1117                hideCompatibilityHelp();
1118                SharedPreferences.Editor editor = Prefs.edit(mContext);
1119                editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true);
1120                editor.apply();
1121            }
1122        });
1123
1124        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1125                ViewGroup.LayoutParams.MATCH_PARENT,
1126                ViewGroup.LayoutParams.MATCH_PARENT,
1127                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
1128                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1129                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
1130                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
1131                PixelFormat.TRANSLUCENT);
1132        lp.setTitle("CompatibilityModeDialog");
1133        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
1134                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
1135        lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade
1136
1137        WindowManagerImpl.getDefault().addView(mCompatibilityHelpDialog, lp);
1138    }
1139
1140    private void hideCompatibilityHelp() {
1141        if (mCompatibilityHelpDialog != null) {
1142            WindowManagerImpl.getDefault().removeView(mCompatibilityHelpDialog);
1143            mCompatibilityHelpDialog = null;
1144        }
1145    }
1146
1147    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1148        mInputMethodSwitchButton.setImeWindowStatus(token,
1149                (vis & InputMethodService.IME_ACTIVE) != 0);
1150        updateNotificationIcons();
1151        mInputMethodsPanel.setImeToken(token);
1152
1153        boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS)
1154            || ((vis & InputMethodService.IME_VISIBLE) != 0);
1155        mAltBackButtonEnabledForIme = altBack;
1156
1157        mCommandQueue.setNavigationIconHints(
1158                altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT)
1159                        : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT));
1160
1161        if (FAKE_SPACE_BAR) {
1162            mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0)
1163                    ? View.VISIBLE : View.GONE);
1164        }
1165    }
1166
1167    @Override
1168    public void onRecentsPanelVisibilityChanged(boolean visible) {
1169        boolean altBack = visible || mAltBackButtonEnabledForIme;
1170        mCommandQueue.setNavigationIconHints(
1171                altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT)
1172                        : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT));
1173    }
1174
1175    @Override
1176    public void setHardKeyboardStatus(boolean available, boolean enabled) {
1177        if (DEBUG) {
1178            Slog.d(TAG, "Set hard keyboard status: available=" + available
1179                    + ", enabled=" + enabled);
1180        }
1181        mInputMethodSwitchButton.setHardKeyboardStatus(available);
1182        updateNotificationIcons();
1183        mInputMethodsPanel.setHardKeyboardStatus(available, enabled);
1184    }
1185
1186    @Override
1187    public void onHardKeyboardEnabledChange(boolean enabled) {
1188        try {
1189            mBarService.setHardKeyboardEnabled(enabled);
1190        } catch (RemoteException ex) {
1191        }
1192    }
1193
1194    private boolean isImmersive() {
1195        try {
1196            return ActivityManagerNative.getDefault().isTopActivityImmersive();
1197            //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
1198        } catch (RemoteException ex) {
1199            // the end is nigh
1200            return false;
1201        }
1202    }
1203
1204    @Override
1205    protected void setAreThereNotifications() {
1206        if (mNotificationPanel != null) {
1207            mNotificationPanel.setClearable(mNotificationData.hasClearableItems());
1208        }
1209    }
1210
1211    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
1212        public void onClick(View v) {
1213            if (v == mRecentButton) {
1214                onClickRecentButton();
1215            } else if (v == mInputMethodSwitchButton) {
1216                onClickInputMethodSwitchButton();
1217            } else if (v == mCompatModeButton) {
1218                onClickCompatModeButton();
1219            }
1220        }
1221    };
1222
1223    public void onClickRecentButton() {
1224        if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled);
1225        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
1226            int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
1227                ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
1228            mHandler.removeMessages(msg);
1229            mHandler.sendEmptyMessage(msg);
1230        }
1231    }
1232
1233    public void onClickInputMethodSwitchButton() {
1234        if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled);
1235        int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ?
1236                MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL;
1237        mHandler.removeMessages(msg);
1238        mHandler.sendEmptyMessage(msg);
1239    }
1240
1241    public void onClickCompatModeButton() {
1242        int msg = (mCompatModePanel.getVisibility() == View.GONE) ?
1243                MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL;
1244        mHandler.removeMessages(msg);
1245        mHandler.sendEmptyMessage(msg);
1246    }
1247
1248    private class NotificationTriggerTouchListener implements View.OnTouchListener {
1249        VelocityTracker mVT;
1250        float mInitialTouchX, mInitialTouchY;
1251        int mTouchSlop;
1252
1253        public NotificationTriggerTouchListener() {
1254            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
1255        }
1256
1257        private Runnable mHiliteOnR = new Runnable() { public void run() {
1258            mNotificationArea.setBackgroundResource(
1259                com.android.internal.R.drawable.list_selector_pressed_holo_dark);
1260        }};
1261        public void hilite(final boolean on) {
1262            if (on) {
1263                mNotificationArea.postDelayed(mHiliteOnR, 100);
1264            } else {
1265                mNotificationArea.removeCallbacks(mHiliteOnR);
1266                mNotificationArea.setBackgroundDrawable(null);
1267            }
1268        }
1269
1270        public boolean onTouch(View v, MotionEvent event) {
1271//            Slog.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)",
1272//                        event.getX(),
1273//                        event.getY(),
1274//                        mInitialTouchX,
1275//                        mInitialTouchY));
1276
1277            if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
1278                return true;
1279            }
1280
1281            final int action = event.getAction();
1282            switch (action) {
1283                case MotionEvent.ACTION_DOWN:
1284                    mVT = VelocityTracker.obtain();
1285                    mInitialTouchX = event.getX();
1286                    mInitialTouchY = event.getY();
1287                    hilite(true);
1288                    // fall through
1289                case MotionEvent.ACTION_OUTSIDE:
1290                case MotionEvent.ACTION_MOVE:
1291                    // check for fling
1292                    if (mVT != null) {
1293                        mVT.addMovement(event);
1294                        mVT.computeCurrentVelocity(1000); // pixels per second
1295                        // require a little more oomph once we're already in peekaboo mode
1296                        if (mVT.getYVelocity() < -mNotificationFlingVelocity) {
1297                            animateExpand();
1298                            visibilityChanged(true);
1299                            hilite(false);
1300                            mVT.recycle();
1301                            mVT = null;
1302                        }
1303                    }
1304                    return true;
1305                case MotionEvent.ACTION_UP:
1306                case MotionEvent.ACTION_CANCEL:
1307                    hilite(false);
1308                    if (mVT != null) {
1309                        if (action == MotionEvent.ACTION_UP
1310                         // was this a sloppy tap?
1311                         && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop
1312                         && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3)
1313                         // dragging off the bottom doesn't count
1314                         && (int)event.getY() < v.getBottom()) {
1315                            animateExpand();
1316                            visibilityChanged(true);
1317                            v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
1318                            v.playSoundEffect(SoundEffectConstants.CLICK);
1319                        }
1320
1321                        mVT.recycle();
1322                        mVT = null;
1323                        return true;
1324                    }
1325            }
1326            return false;
1327        }
1328    }
1329
1330    public void resetNotificationPeekFadeTimer() {
1331        if (DEBUG) {
1332            Slog.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY
1333                + "ms from now");
1334        }
1335        mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK);
1336        mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK,
1337                NOTIFICATION_PEEK_FADE_DELAY);
1338    }
1339
1340    private class NotificationIconTouchListener implements View.OnTouchListener {
1341        VelocityTracker mVT;
1342        int mPeekIndex;
1343        float mInitialTouchX, mInitialTouchY;
1344        int mTouchSlop;
1345
1346        public NotificationIconTouchListener() {
1347            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
1348        }
1349
1350        public boolean onTouch(View v, MotionEvent event) {
1351            boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
1352            boolean panelShowing = mNotificationPanel.isShowing();
1353            if (panelShowing) return false;
1354
1355            int numIcons = mIconLayout.getChildCount();
1356            int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth());
1357            if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1;
1358            else if (newPeekIndex < 0) newPeekIndex = 0;
1359
1360            final int action = event.getAction();
1361            switch (action) {
1362                case MotionEvent.ACTION_DOWN:
1363                    mVT = VelocityTracker.obtain();
1364                    mInitialTouchX = event.getX();
1365                    mInitialTouchY = event.getY();
1366                    mPeekIndex = -1;
1367
1368                    // fall through
1369                case MotionEvent.ACTION_OUTSIDE:
1370                case MotionEvent.ACTION_MOVE:
1371                    // peek and switch icons if necessary
1372
1373                    if (newPeekIndex != mPeekIndex) {
1374                        mPeekIndex = newPeekIndex;
1375
1376                        if (DEBUG) Slog.d(TAG, "will peek at notification #" + mPeekIndex);
1377                        Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
1378                        peekMsg.arg1 = mPeekIndex;
1379
1380                        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1381
1382                        if (peeking) {
1383                            // no delay if we're scrubbing left-right
1384                            mHandler.sendMessage(peekMsg);
1385                        } else {
1386                            // wait for fling
1387                            mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH);
1388                        }
1389                    }
1390
1391                    // check for fling
1392                    if (mVT != null) {
1393                        mVT.addMovement(event);
1394                        mVT.computeCurrentVelocity(1000); // pixels per second
1395                        // require a little more oomph once we're already in peekaboo mode
1396                        if (!panelShowing && (
1397                               (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
1398                            || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
1399                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1400                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
1401                            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
1402                            mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
1403                        }
1404                    }
1405                    return true;
1406                case MotionEvent.ACTION_UP:
1407                case MotionEvent.ACTION_CANCEL:
1408                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1409                    if (!peeking) {
1410                        if (action == MotionEvent.ACTION_UP
1411                                // was this a sloppy tap?
1412                                && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop
1413                                && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3)
1414                                // dragging off the bottom doesn't count
1415                                && (int)event.getY() < v.getBottom()) {
1416                            Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
1417                            peekMsg.arg1 = mPeekIndex;
1418                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
1419                            mHandler.sendMessage(peekMsg);
1420
1421                            v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
1422                            v.playSoundEffect(SoundEffectConstants.CLICK);
1423
1424                            peeking = true; // not technically true yet, but the next line will run
1425                        }
1426                    }
1427
1428                    if (peeking) {
1429                        resetNotificationPeekFadeTimer();
1430                    }
1431
1432                    mVT.recycle();
1433                    mVT = null;
1434                    return true;
1435            }
1436            return false;
1437        }
1438    }
1439
1440    private void reloadAllNotificationIcons() {
1441        if (mIconLayout == null) return;
1442        mIconLayout.removeAllViews();
1443        updateNotificationIcons();
1444    }
1445
1446    @Override
1447    protected void updateNotificationIcons() {
1448        // XXX: need to implement a new limited linear layout class
1449        // to avoid removing & readding everything
1450
1451        if (mIconLayout == null) return;
1452
1453        // first, populate the main notification panel
1454        loadNotificationPanel();
1455
1456        final LinearLayout.LayoutParams params
1457            = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
1458
1459        // alternate behavior in DND mode
1460        if (mNotificationDNDMode) {
1461            if (mIconLayout.getChildCount() == 0) {
1462                final Notification dndNotification = new Notification.Builder(mContext)
1463                    .setContentTitle(mContext.getText(R.string.notifications_off_title))
1464                    .setContentText(mContext.getText(R.string.notifications_off_text))
1465                    .setSmallIcon(R.drawable.ic_notification_dnd)
1466                    .setOngoing(true)
1467                    .getNotification();
1468
1469                final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd",
1470                        dndNotification);
1471                iconView.setImageResource(R.drawable.ic_notification_dnd);
1472                iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1473                iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0);
1474
1475                mNotificationDNDDummyEntry = new NotificationData.Entry(
1476                        null,
1477                        new StatusBarNotification("", 0, "", 0, 0, Notification.PRIORITY_MAX, dndNotification),
1478                        iconView);
1479
1480                mIconLayout.addView(iconView, params);
1481            }
1482
1483            return;
1484        } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) {
1485            // if icons are disabled but we're not in DND mode, this is probably Setup and we should
1486            // just leave the area totally empty
1487            return;
1488        }
1489
1490        int N = mNotificationData.size();
1491
1492        if (DEBUG) {
1493            Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
1494        }
1495
1496        ArrayList<View> toShow = new ArrayList<View>();
1497
1498        // Extra Special Icons
1499        // The IME switcher and compatibility mode icons take the place of notifications. You didn't
1500        // need to see all those new emails, did you?
1501        int maxNotificationIconsCount = mMaxNotificationIcons;
1502        if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount --;
1503        if (mCompatModeButton.getVisibility()        != View.GONE) maxNotificationIconsCount --;
1504
1505        for (int i=0; toShow.size()< maxNotificationIconsCount; i++) {
1506            if (i >= N) break;
1507            Entry ent = mNotificationData.get(N-i-1);
1508            if (ent.notification.score >= HIDE_ICONS_BELOW_SCORE) {
1509                toShow.add(ent.icon);
1510            }
1511        }
1512
1513        ArrayList<View> toRemove = new ArrayList<View>();
1514        for (int i=0; i<mIconLayout.getChildCount(); i++) {
1515            View child = mIconLayout.getChildAt(i);
1516            if (!toShow.contains(child)) {
1517                toRemove.add(child);
1518            }
1519        }
1520
1521        for (View remove : toRemove) {
1522            mIconLayout.removeView(remove);
1523        }
1524
1525        for (int i=0; i<toShow.size(); i++) {
1526            View v = toShow.get(i);
1527            v.setPadding(mIconHPadding, 0, mIconHPadding, 0);
1528            if (v.getParent() == null) {
1529                mIconLayout.addView(v, i, params);
1530            }
1531        }
1532    }
1533
1534    private void loadNotificationPanel() {
1535        int N = mNotificationData.size();
1536
1537        ArrayList<View> toShow = new ArrayList<View>();
1538
1539        for (int i=0; i<N; i++) {
1540            View row = mNotificationData.get(N-i-1).row;
1541            toShow.add(row);
1542        }
1543
1544        ArrayList<View> toRemove = new ArrayList<View>();
1545        for (int i=0; i<mPile.getChildCount(); i++) {
1546            View child = mPile.getChildAt(i);
1547            if (!toShow.contains(child)) {
1548                toRemove.add(child);
1549            }
1550        }
1551
1552        for (View remove : toRemove) {
1553            mPile.removeView(remove);
1554        }
1555
1556        for (int i=0; i<toShow.size(); i++) {
1557            View v = toShow.get(i);
1558            if (v.getParent() == null) {
1559                // the notification panel has the most important things at the bottom
1560                mPile.addView(v, N-1-i);
1561            }
1562        }
1563
1564        mNotificationPanel.setNotificationCount(N);
1565    }
1566
1567    @Override
1568    protected void workAroundBadLayerDrawableOpacity(View v) {
1569        Drawable bgd = v.getBackground();
1570        if (!(bgd instanceof LayerDrawable)) return;
1571
1572        LayerDrawable d = (LayerDrawable) bgd;
1573        v.setBackgroundDrawable(null);
1574        d.setOpacity(PixelFormat.TRANSLUCENT);
1575        v.setBackgroundDrawable(d);
1576    }
1577
1578    public void clearAll() {
1579        try {
1580            mBarService.onClearAllNotifications();
1581        } catch (RemoteException ex) {
1582            // system process is dead if we're here.
1583        }
1584        animateCollapse();
1585        visibilityChanged(false);
1586    }
1587
1588    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1589        public void onReceive(Context context, Intent intent) {
1590            String action = intent.getAction();
1591            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
1592                || Intent.ACTION_SCREEN_OFF.equals(action)) {
1593                boolean excludeRecents = false;
1594                if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
1595                    String reason = intent.getStringExtra("reason");
1596                    if (reason != null) {
1597                        excludeRecents = reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS);
1598                    }
1599                }
1600                if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1601                    // If we're turning the screen off, we want to hide the
1602                    // recents panel with no animation
1603                    // TODO: hide other things, like the notification tray,
1604                    // with no animation as well
1605                    mRecentsPanel.show(false, false);
1606                    excludeRecents = true;
1607                }
1608                animateCollapse(excludeRecents);
1609            }
1610        }
1611    };
1612
1613    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1614        pw.print("mDisabled=0x");
1615        pw.println(Integer.toHexString(mDisabled));
1616        pw.println("mNetworkController:");
1617        mNetworkController.dump(fd, pw, args);
1618    }
1619
1620    @Override
1621    protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
1622        return parent.indexOfChild(entry.row) == parent.getChildCount()-1;
1623    }
1624
1625    @Override
1626    protected void haltTicker() {
1627        mTicker.halt();
1628    }
1629
1630    @Override
1631    protected void updateExpandedViewPos(int expandedPosition) {
1632    }
1633
1634    @Override
1635    protected boolean shouldDisableNavbarGestures() {
1636        return mNotificationPanel.getVisibility() == View.VISIBLE
1637                || (mDisabled & StatusBarManager.DISABLE_HOME) != 0;
1638    }
1639}
1640
1641
1642