PageRenderTime 101ms CodeModel.GetById 13ms app.highlight 78ms RepoModel.GetById 1ms app.codeStats 0ms

/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java

https://github.com/aizuzi/platform_frameworks_base
Java | 1104 lines | 868 code | 138 blank | 98 comment | 215 complexity | 91e365098d7f41352ae7ce4488bcc562 MD5 | raw file
   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;
  18
  19import android.app.ActivityManager;
  20import android.app.ActivityManagerNative;
  21import android.app.Notification;
  22import android.app.PendingIntent;
  23import android.app.TaskStackBuilder;
  24import android.content.BroadcastReceiver;
  25import android.content.Context;
  26import android.content.Intent;
  27import android.content.IntentFilter;
  28import android.content.pm.ApplicationInfo;
  29import android.content.pm.PackageManager.NameNotFoundException;
  30import android.content.res.Configuration;
  31import android.database.ContentObserver;
  32import android.graphics.Rect;
  33import android.net.Uri;
  34import android.os.Build;
  35import android.os.Handler;
  36import android.os.IBinder;
  37import android.os.Message;
  38import android.os.PowerManager;
  39import android.os.RemoteException;
  40import android.os.ServiceManager;
  41import android.os.UserHandle;
  42import android.provider.Settings;
  43import android.service.dreams.DreamService;
  44import android.service.dreams.IDreamManager;
  45import android.service.notification.StatusBarNotification;
  46import android.text.TextUtils;
  47import android.util.Log;
  48import android.view.Display;
  49import android.view.IWindowManager;
  50import android.view.LayoutInflater;
  51import android.view.MenuItem;
  52import android.view.MotionEvent;
  53import android.view.View;
  54import android.view.ViewGroup;
  55import android.view.ViewGroup.LayoutParams;
  56import android.view.WindowManager;
  57import android.view.WindowManagerGlobal;
  58import android.widget.ImageView;
  59import android.widget.LinearLayout;
  60import android.widget.PopupMenu;
  61import android.widget.RemoteViews;
  62import android.widget.TextView;
  63
  64import com.android.internal.statusbar.IStatusBarService;
  65import com.android.internal.statusbar.StatusBarIcon;
  66import com.android.internal.statusbar.StatusBarIconList;
  67import com.android.internal.widget.SizeAdaptiveLayout;
  68import com.android.systemui.R;
  69import com.android.systemui.RecentsComponent;
  70import com.android.systemui.SearchPanelView;
  71import com.android.systemui.SystemUI;
  72import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
  73import com.android.systemui.statusbar.policy.NotificationRowLayout;
  74
  75import java.util.ArrayList;
  76import java.util.Locale;
  77
  78public abstract class BaseStatusBar extends SystemUI implements
  79        CommandQueue.Callbacks {
  80    public static final String TAG = "StatusBar";
  81    public static final boolean DEBUG = false;
  82    public static final boolean MULTIUSER_DEBUG = false;
  83
  84    protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
  85    protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
  86    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
  87    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
  88    protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
  89    protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
  90    protected static final int MSG_SHOW_HEADS_UP = 1026;
  91    protected static final int MSG_HIDE_HEADS_UP = 1027;
  92    protected static final int MSG_ESCALATE_HEADS_UP = 1028;
  93
  94    protected static final boolean ENABLE_HEADS_UP = true;
  95    // scores above this threshold should be displayed in heads up mode.
  96    protected static final int INTERRUPTION_THRESHOLD = 11;
  97    protected static final String SETTING_HEADS_UP = "heads_up_enabled";
  98
  99    // Should match the value in PhoneWindowManager
 100    public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
 101
 102    public static final int EXPANDED_LEAVE_ALONE = -10000;
 103    public static final int EXPANDED_FULL_OPEN = -10001;
 104
 105    protected CommandQueue mCommandQueue;
 106    protected IStatusBarService mBarService;
 107    protected H mHandler = createHandler();
 108
 109    // all notifications
 110    protected NotificationData mNotificationData = new NotificationData();
 111    protected NotificationRowLayout mPile;
 112
 113    protected NotificationData.Entry mInterruptingNotificationEntry;
 114    protected long mInterruptingNotificationTime;
 115
 116    // used to notify status bar for suppressing notification LED
 117    protected boolean mPanelSlightlyVisible;
 118
 119    // Search panel
 120    protected SearchPanelView mSearchPanelView;
 121
 122    protected PopupMenu mNotificationBlamePopup;
 123
 124    protected int mCurrentUserId = 0;
 125
 126    protected int mLayoutDirection = -1; // invalid
 127    private Locale mLocale;
 128    protected boolean mUseHeadsUp = false;
 129
 130    protected IDreamManager mDreamManager;
 131    PowerManager mPowerManager;
 132    protected int mRowHeight;
 133
 134    // UI-specific methods
 135
 136    /**
 137     * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
 138     * and add them to the window manager.
 139     */
 140    protected abstract void createAndAddWindows();
 141
 142    protected WindowManager mWindowManager;
 143    protected IWindowManager mWindowManagerService;
 144    protected abstract void refreshLayout(int layoutDirection);
 145
 146    protected Display mDisplay;
 147
 148    private boolean mDeviceProvisioned = false;
 149
 150    private RecentsComponent mRecents;
 151
 152    public IStatusBarService getStatusBarService() {
 153        return mBarService;
 154    }
 155
 156    public boolean isDeviceProvisioned() {
 157        return mDeviceProvisioned;
 158    }
 159
 160    private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
 161        @Override
 162        public void onChange(boolean selfChange) {
 163            final boolean provisioned = 0 != Settings.Global.getInt(
 164                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
 165            if (provisioned != mDeviceProvisioned) {
 166                mDeviceProvisioned = provisioned;
 167                updateNotificationIcons();
 168            }
 169        }
 170    };
 171
 172    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
 173        @Override
 174        public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
 175            if (DEBUG) {
 176                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
 177            }
 178            final boolean isActivity = pendingIntent.isActivity();
 179            if (isActivity) {
 180                try {
 181                    // The intent we are sending is for the application, which
 182                    // won't have permission to immediately start an activity after
 183                    // the user switches to home.  We know it is safe to do at this
 184                    // point, so make sure new activity switches are now allowed.
 185                    ActivityManagerNative.getDefault().resumeAppSwitches();
 186                    // Also, notifications can be launched from the lock screen,
 187                    // so dismiss the lock screen when the activity starts.
 188                    ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
 189                } catch (RemoteException e) {
 190                }
 191            }
 192
 193            boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
 194
 195            if (isActivity && handled) {
 196                // close the shade if it was open
 197                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
 198                visibilityChanged(false);
 199            }
 200            return handled;
 201        }
 202    };
 203
 204    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
 205        @Override
 206        public void onReceive(Context context, Intent intent) {
 207            String action = intent.getAction();
 208            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
 209                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
 210                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
 211                userSwitched(mCurrentUserId);
 212            }
 213        }
 214    };
 215
 216    public void start() {
 217        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
 218        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
 219        mDisplay = mWindowManager.getDefaultDisplay();
 220
 221        mDreamManager = IDreamManager.Stub.asInterface(
 222                ServiceManager.checkService(DreamService.DREAM_SERVICE));
 223        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 224
 225        mProvisioningObserver.onChange(false); // set up
 226        mContext.getContentResolver().registerContentObserver(
 227                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
 228                mProvisioningObserver);
 229
 230        mBarService = IStatusBarService.Stub.asInterface(
 231                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 232
 233        mRecents = getComponent(RecentsComponent.class);
 234
 235        mLocale = mContext.getResources().getConfiguration().locale;
 236        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
 237
 238        // Connect in to the status bar manager service
 239        StatusBarIconList iconList = new StatusBarIconList();
 240        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
 241        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
 242        mCommandQueue = new CommandQueue(this, iconList);
 243
 244        int[] switches = new int[7];
 245        ArrayList<IBinder> binders = new ArrayList<IBinder>();
 246        try {
 247            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
 248                    switches, binders);
 249        } catch (RemoteException ex) {
 250            // If the system process isn't there we're doomed anyway.
 251        }
 252
 253        createAndAddWindows();
 254
 255        disable(switches[0]);
 256        setSystemUiVisibility(switches[1], 0xffffffff);
 257        topAppWindowChanged(switches[2] != 0);
 258        // StatusBarManagerService has a back up of IME token and it's restored here.
 259        setImeWindowStatus(binders.get(0), switches[3], switches[4]);
 260        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
 261
 262        // Set up the initial icon state
 263        int N = iconList.size();
 264        int viewIndex = 0;
 265        for (int i=0; i<N; i++) {
 266            StatusBarIcon icon = iconList.getIcon(i);
 267            if (icon != null) {
 268                addIcon(iconList.getSlot(i), i, viewIndex, icon);
 269                viewIndex++;
 270            }
 271        }
 272
 273        // Set up the initial notification state
 274        N = notificationKeys.size();
 275        if (N == notifications.size()) {
 276            for (int i=0; i<N; i++) {
 277                addNotification(notificationKeys.get(i), notifications.get(i));
 278            }
 279        } else {
 280            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
 281                    + " notifications=" + notifications.size());
 282        }
 283
 284        if (DEBUG) {
 285            Log.d(TAG, String.format(
 286                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
 287                   iconList.size(),
 288                   switches[0],
 289                   switches[1],
 290                   switches[2],
 291                   switches[3]
 292                   ));
 293        }
 294
 295        mCurrentUserId = ActivityManager.getCurrentUser();
 296
 297        IntentFilter filter = new IntentFilter();
 298        filter.addAction(Intent.ACTION_USER_SWITCHED);
 299        mContext.registerReceiver(mBroadcastReceiver, filter);
 300    }
 301
 302    public void userSwitched(int newUserId) {
 303        // should be overridden
 304    }
 305
 306    public boolean notificationIsForCurrentUser(StatusBarNotification n) {
 307        final int thisUserId = mCurrentUserId;
 308        final int notificationUserId = n.getUserId();
 309        if (DEBUG && MULTIUSER_DEBUG) {
 310            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
 311                    n, thisUserId, notificationUserId));
 312        }
 313        return notificationUserId == UserHandle.USER_ALL
 314                || thisUserId == notificationUserId;
 315    }
 316
 317    @Override
 318    protected void onConfigurationChanged(Configuration newConfig) {
 319        final Locale locale = mContext.getResources().getConfiguration().locale;
 320        final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
 321        if (! locale.equals(mLocale) || ld != mLayoutDirection) {
 322            if (DEBUG) {
 323                Log.v(TAG, String.format(
 324                        "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
 325                        locale, ld));
 326            }
 327            mLocale = locale;
 328            mLayoutDirection = ld;
 329            refreshLayout(ld);
 330        }
 331    }
 332
 333    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
 334        View vetoButton = row.findViewById(R.id.veto);
 335        if (n.isClearable() || (mInterruptingNotificationEntry != null
 336                && mInterruptingNotificationEntry.row == row)) {
 337            final String _pkg = n.getPackageName();
 338            final String _tag = n.getTag();
 339            final int _id = n.getId();
 340            vetoButton.setOnClickListener(new View.OnClickListener() {
 341                    public void onClick(View v) {
 342                        // Accessibility feedback
 343                        v.announceForAccessibility(
 344                                mContext.getString(R.string.accessibility_notification_dismissed));
 345                        try {
 346                            mBarService.onNotificationClear(_pkg, _tag, _id);
 347
 348                        } catch (RemoteException ex) {
 349                            // system process is dead if we're here.
 350                        }
 351                    }
 352                });
 353            vetoButton.setVisibility(View.VISIBLE);
 354        } else {
 355            vetoButton.setVisibility(View.GONE);
 356        }
 357        vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
 358        return vetoButton;
 359    }
 360
 361
 362    protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
 363        if (sbn.getNotification().contentView.getLayoutId() !=
 364                com.android.internal.R.layout.notification_template_base) {
 365            int version = 0;
 366            try {
 367                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
 368                version = info.targetSdkVersion;
 369            } catch (NameNotFoundException ex) {
 370                Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
 371            }
 372            if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
 373                content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
 374            } else {
 375                content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
 376            }
 377        }
 378    }
 379
 380    private void startApplicationDetailsActivity(String packageName) {
 381        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
 382                Uri.fromParts("package", packageName, null));
 383        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
 384        TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
 385                null, UserHandle.CURRENT);
 386    }
 387
 388    protected View.OnLongClickListener getNotificationLongClicker() {
 389        return new View.OnLongClickListener() {
 390            @Override
 391            public boolean onLongClick(View v) {
 392                final String packageNameF = (String) v.getTag();
 393                if (packageNameF == null) return false;
 394                if (v.getWindowToken() == null) return false;
 395                mNotificationBlamePopup = new PopupMenu(mContext, v);
 396                mNotificationBlamePopup.getMenuInflater().inflate(
 397                        R.menu.notification_popup_menu,
 398                        mNotificationBlamePopup.getMenu());
 399                mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
 400                    public boolean onMenuItemClick(MenuItem item) {
 401                        if (item.getItemId() == R.id.notification_inspect_item) {
 402                            startApplicationDetailsActivity(packageNameF);
 403                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
 404                        } else {
 405                            return false;
 406                        }
 407                        return true;
 408                    }
 409                });
 410                mNotificationBlamePopup.show();
 411
 412                return true;
 413            }
 414        };
 415    }
 416
 417    public void dismissPopups() {
 418        if (mNotificationBlamePopup != null) {
 419            mNotificationBlamePopup.dismiss();
 420            mNotificationBlamePopup = null;
 421        }
 422    }
 423
 424    public void onHeadsUpDismissed() {
 425    }
 426
 427    @Override
 428    public void toggleRecentApps() {
 429        int msg = MSG_TOGGLE_RECENTS_PANEL;
 430        mHandler.removeMessages(msg);
 431        mHandler.sendEmptyMessage(msg);
 432    }
 433
 434    @Override
 435    public void preloadRecentApps() {
 436        int msg = MSG_PRELOAD_RECENT_APPS;
 437        mHandler.removeMessages(msg);
 438        mHandler.sendEmptyMessage(msg);
 439    }
 440
 441    @Override
 442    public void cancelPreloadRecentApps() {
 443        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
 444        mHandler.removeMessages(msg);
 445        mHandler.sendEmptyMessage(msg);
 446    }
 447
 448    @Override
 449    public void showSearchPanel() {
 450        int msg = MSG_OPEN_SEARCH_PANEL;
 451        mHandler.removeMessages(msg);
 452        mHandler.sendEmptyMessage(msg);
 453    }
 454
 455    @Override
 456    public void hideSearchPanel() {
 457        int msg = MSG_CLOSE_SEARCH_PANEL;
 458        mHandler.removeMessages(msg);
 459        mHandler.sendEmptyMessage(msg);
 460    }
 461
 462    protected abstract WindowManager.LayoutParams getSearchLayoutParams(
 463            LayoutParams layoutParams);
 464
 465    protected void updateSearchPanel() {
 466        // Search Panel
 467        boolean visible = false;
 468        if (mSearchPanelView != null) {
 469            visible = mSearchPanelView.isShowing();
 470            mWindowManager.removeView(mSearchPanelView);
 471        }
 472
 473        // Provide SearchPanel with a temporary parent to allow layout params to work.
 474        LinearLayout tmpRoot = new LinearLayout(mContext);
 475        mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
 476                 R.layout.status_bar_search_panel, tmpRoot, false);
 477        mSearchPanelView.setOnTouchListener(
 478                 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
 479        mSearchPanelView.setVisibility(View.GONE);
 480
 481        WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
 482
 483        mWindowManager.addView(mSearchPanelView, lp);
 484        mSearchPanelView.setBar(this);
 485        if (visible) {
 486            mSearchPanelView.show(true, false);
 487        }
 488    }
 489
 490    protected H createHandler() {
 491         return new H();
 492    }
 493
 494    static void sendCloseSystemWindows(Context context, String reason) {
 495        if (ActivityManagerNative.isSystemReady()) {
 496            try {
 497                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
 498            } catch (RemoteException e) {
 499            }
 500        }
 501    }
 502
 503    protected abstract View getStatusBarView();
 504
 505    protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
 506        // additional optimization when we have software system buttons - start loading the recent
 507        // tasks on touch down
 508        @Override
 509        public boolean onTouch(View v, MotionEvent event) {
 510            int action = event.getAction() & MotionEvent.ACTION_MASK;
 511            if (action == MotionEvent.ACTION_DOWN) {
 512                preloadRecentTasksList();
 513            } else if (action == MotionEvent.ACTION_CANCEL) {
 514                cancelPreloadingRecentTasksList();
 515            } else if (action == MotionEvent.ACTION_UP) {
 516                if (!v.isPressed()) {
 517                    cancelPreloadingRecentTasksList();
 518                }
 519
 520            }
 521            return false;
 522        }
 523    };
 524
 525    protected void toggleRecentsActivity() {
 526        if (mRecents != null) {
 527            mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
 528        }
 529    }
 530
 531    protected void preloadRecentTasksList() {
 532        if (mRecents != null) {
 533            mRecents.preloadRecentTasksList();
 534        }
 535    }
 536
 537    protected void cancelPreloadingRecentTasksList() {
 538        if (mRecents != null) {
 539            mRecents.cancelPreloadingRecentTasksList();
 540        }
 541    }
 542
 543    protected void closeRecents() {
 544        if (mRecents != null) {
 545            mRecents.closeRecents();
 546        }
 547    }
 548
 549    public abstract void resetHeadsUpDecayTimer();
 550
 551    protected class H extends Handler {
 552        public void handleMessage(Message m) {
 553            Intent intent;
 554            switch (m.what) {
 555             case MSG_TOGGLE_RECENTS_PANEL:
 556                 toggleRecentsActivity();
 557                 break;
 558             case MSG_CLOSE_RECENTS_PANEL:
 559                 closeRecents();
 560                 break;
 561             case MSG_PRELOAD_RECENT_APPS:
 562                  preloadRecentTasksList();
 563                  break;
 564             case MSG_CANCEL_PRELOAD_RECENT_APPS:
 565                  cancelPreloadingRecentTasksList();
 566                  break;
 567             case MSG_OPEN_SEARCH_PANEL:
 568                 if (DEBUG) Log.d(TAG, "opening search panel");
 569                 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
 570                     mSearchPanelView.show(true, true);
 571                     onShowSearchPanel();
 572                 }
 573                 break;
 574             case MSG_CLOSE_SEARCH_PANEL:
 575                 if (DEBUG) Log.d(TAG, "closing search panel");
 576                 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
 577                     mSearchPanelView.show(false, true);
 578                     onHideSearchPanel();
 579                 }
 580                 break;
 581            }
 582        }
 583    }
 584
 585    public class TouchOutsideListener implements View.OnTouchListener {
 586        private int mMsg;
 587        private StatusBarPanel mPanel;
 588
 589        public TouchOutsideListener(int msg, StatusBarPanel panel) {
 590            mMsg = msg;
 591            mPanel = panel;
 592        }
 593
 594        public boolean onTouch(View v, MotionEvent ev) {
 595            final int action = ev.getAction();
 596            if (action == MotionEvent.ACTION_OUTSIDE
 597                || (action == MotionEvent.ACTION_DOWN
 598                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
 599                mHandler.removeMessages(mMsg);
 600                mHandler.sendEmptyMessage(mMsg);
 601                return true;
 602            }
 603            return false;
 604        }
 605    }
 606
 607    protected void workAroundBadLayerDrawableOpacity(View v) {
 608    }
 609
 610    protected void onHideSearchPanel() {
 611    }
 612
 613    protected void onShowSearchPanel() {
 614    }
 615
 616    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
 617        int minHeight =
 618                mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
 619        int maxHeight =
 620                mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
 621        StatusBarNotification sbn = entry.notification;
 622        RemoteViews contentView = sbn.getNotification().contentView;
 623        RemoteViews bigContentView = sbn.getNotification().bigContentView;
 624        if (contentView == null) {
 625            return false;
 626        }
 627
 628        // create the row view
 629        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
 630                Context.LAYOUT_INFLATER_SERVICE);
 631        ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
 632                R.layout.status_bar_notification_row, parent, false);
 633
 634        // for blaming (see SwipeHelper.setLongPressListener)
 635        row.setTag(sbn.getPackageName());
 636
 637        workAroundBadLayerDrawableOpacity(row);
 638        View vetoButton = updateNotificationVetoButton(row, sbn);
 639        vetoButton.setContentDescription(mContext.getString(
 640                R.string.accessibility_remove_notification));
 641
 642        // NB: the large icon is now handled entirely by the template
 643
 644        // bind the click event to the content area
 645        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
 646        ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
 647
 648        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
 649
 650        PendingIntent contentIntent = sbn.getNotification().contentIntent;
 651        if (contentIntent != null) {
 652            final View.OnClickListener listener = new NotificationClicker(contentIntent,
 653                    sbn.getPackageName(), sbn.getTag(), sbn.getId());
 654            content.setOnClickListener(listener);
 655        } else {
 656            content.setOnClickListener(null);
 657        }
 658
 659        View contentViewLocal = null;
 660        View bigContentViewLocal = null;
 661        try {
 662            contentViewLocal = contentView.apply(mContext, adaptive, mOnClickHandler);
 663            if (bigContentView != null) {
 664                bigContentViewLocal = bigContentView.apply(mContext, adaptive, mOnClickHandler);
 665            }
 666        }
 667        catch (RuntimeException e) {
 668            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
 669            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
 670            return false;
 671        }
 672
 673        if (contentViewLocal != null) {
 674            SizeAdaptiveLayout.LayoutParams params =
 675                    new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
 676            params.minHeight = minHeight;
 677            params.maxHeight = minHeight;
 678            adaptive.addView(contentViewLocal, params);
 679        }
 680        if (bigContentViewLocal != null) {
 681            SizeAdaptiveLayout.LayoutParams params =
 682                    new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
 683            params.minHeight = minHeight+1;
 684            params.maxHeight = maxHeight;
 685            adaptive.addView(bigContentViewLocal, params);
 686        }
 687        row.setDrawingCacheEnabled(true);
 688
 689        applyLegacyRowBackground(sbn, content);
 690
 691        if (MULTIUSER_DEBUG) {
 692            TextView debug = (TextView) row.findViewById(R.id.debug_info);
 693            if (debug != null) {
 694                debug.setVisibility(View.VISIBLE);
 695                debug.setText("U " + entry.notification.getUserId());
 696            }
 697        }
 698        entry.row = row;
 699        entry.row.setRowHeight(mRowHeight);
 700        entry.content = content;
 701        entry.expanded = contentViewLocal;
 702        entry.setBigContentView(bigContentViewLocal);
 703
 704        return true;
 705    }
 706
 707    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
 708        return new NotificationClicker(intent, pkg, tag, id);
 709    }
 710
 711    protected class NotificationClicker implements View.OnClickListener {
 712        private PendingIntent mIntent;
 713        private String mPkg;
 714        private String mTag;
 715        private int mId;
 716
 717        public NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
 718            mIntent = intent;
 719            mPkg = pkg;
 720            mTag = tag;
 721            mId = id;
 722        }
 723
 724        public void onClick(View v) {
 725            try {
 726                // The intent we are sending is for the application, which
 727                // won't have permission to immediately start an activity after
 728                // the user switches to home.  We know it is safe to do at this
 729                // point, so make sure new activity switches are now allowed.
 730                ActivityManagerNative.getDefault().resumeAppSwitches();
 731                // Also, notifications can be launched from the lock screen,
 732                // so dismiss the lock screen when the activity starts.
 733                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
 734            } catch (RemoteException e) {
 735            }
 736
 737            if (mIntent != null) {
 738                int[] pos = new int[2];
 739                v.getLocationOnScreen(pos);
 740                Intent overlay = new Intent();
 741                overlay.setSourceBounds(
 742                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
 743                try {
 744                    mIntent.send(mContext, 0, overlay);
 745                } catch (PendingIntent.CanceledException e) {
 746                    // the stack trace isn't very helpful here.  Just log the exception message.
 747                    Log.w(TAG, "Sending contentIntent failed: " + e);
 748                }
 749
 750                KeyguardTouchDelegate.getInstance(mContext).dismiss();
 751            }
 752
 753            try {
 754                mBarService.onNotificationClick(mPkg, mTag, mId);
 755            } catch (RemoteException ex) {
 756                // system process is dead if we're here.
 757            }
 758
 759            // close the shade if it was open
 760            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
 761            visibilityChanged(false);
 762        }
 763    }
 764    /**
 765     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
 766     * This was added last-minute and is inconsistent with the way the rest of the notifications
 767     * are handled, because the notification isn't really cancelled.  The lights are just
 768     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
 769     * this is what he wants. (see bug 1131461)
 770     */
 771    protected void visibilityChanged(boolean visible) {
 772        if (mPanelSlightlyVisible != visible) {
 773            mPanelSlightlyVisible = visible;
 774            try {
 775                mBarService.onPanelRevealed();
 776            } catch (RemoteException ex) {
 777                // Won't fail unless the world has ended.
 778            }
 779        }
 780    }
 781
 782    /**
 783     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
 784     * about the failure.
 785     *
 786     * WARNING: this will call back into us.  Don't hold any locks.
 787     */
 788    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
 789        removeNotification(key);
 790        try {
 791            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message);
 792        } catch (RemoteException ex) {
 793            // The end is nigh.
 794        }
 795    }
 796
 797    protected StatusBarNotification removeNotificationViews(IBinder key) {
 798        NotificationData.Entry entry = mNotificationData.remove(key);
 799        if (entry == null) {
 800            Log.w(TAG, "removeNotification for unknown key: " + key);
 801            return null;
 802        }
 803        // Remove the expanded view.
 804        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
 805        if (rowParent != null) rowParent.removeView(entry.row);
 806        updateExpansionStates();
 807        updateNotificationIcons();
 808
 809        return entry.notification;
 810    }
 811
 812    protected NotificationData.Entry createNotificationViews(IBinder key,
 813            StatusBarNotification notification) {
 814        if (DEBUG) {
 815            Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification);
 816        }
 817        // Construct the icon.
 818        final StatusBarIconView iconView = new StatusBarIconView(mContext,
 819                notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()),
 820                notification.getNotification());
 821        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
 822
 823        final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
 824                notification.getUser(),
 825                    notification.getNotification().icon,
 826                    notification.getNotification().iconLevel,
 827                    notification.getNotification().number,
 828                    notification.getNotification().tickerText);
 829        if (!iconView.set(ic)) {
 830            handleNotificationError(key, notification, "Couldn't create icon: " + ic);
 831            return null;
 832        }
 833        // Construct the expanded view.
 834        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
 835        if (!inflateViews(entry, mPile)) {
 836            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
 837                    + notification);
 838            return null;
 839        }
 840        return entry;
 841    }
 842
 843    protected void addNotificationViews(NotificationData.Entry entry) {
 844        if (entry == null) {
 845            return;
 846        }
 847        // Add the expanded view and icon.
 848        int pos = mNotificationData.add(entry);
 849        if (DEBUG) {
 850            Log.d(TAG, "addNotificationViews: added at " + pos);
 851        }
 852        updateExpansionStates();
 853        updateNotificationIcons();
 854    }
 855
 856    private void addNotificationViews(IBinder key, StatusBarNotification notification) {
 857        addNotificationViews(createNotificationViews(key, notification));
 858    }
 859
 860    protected void updateExpansionStates() {
 861        int N = mNotificationData.size();
 862        for (int i = 0; i < N; i++) {
 863            NotificationData.Entry entry = mNotificationData.get(i);
 864            if (!entry.row.isUserLocked()) {
 865                if (i == (N-1)) {
 866                    if (DEBUG) Log.d(TAG, "expanding top notification at " + i);
 867                    entry.row.setExpanded(true);
 868                } else {
 869                    if (!entry.row.isUserExpanded()) {
 870                        if (DEBUG) Log.d(TAG, "collapsing notification at " + i);
 871                        entry.row.setExpanded(false);
 872                    } else {
 873                        if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i);
 874                    }
 875                }
 876            } else {
 877                if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i);
 878            }
 879        }
 880    }
 881
 882    protected abstract void haltTicker();
 883    protected abstract void setAreThereNotifications();
 884    protected abstract void updateNotificationIcons();
 885    protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
 886    protected abstract void updateExpandedViewPos(int expandedPosition);
 887    protected abstract int getExpandedViewMaxHeight();
 888    protected abstract boolean shouldDisableNavbarGestures();
 889
 890    protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
 891        return parent != null && parent.indexOfChild(entry.row) == 0;
 892    }
 893
 894    public void updateNotification(IBinder key, StatusBarNotification notification) {
 895        if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
 896
 897        final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
 898        if (oldEntry == null) {
 899            Log.w(TAG, "updateNotification for unknown key: " + key);
 900            return;
 901        }
 902
 903        final StatusBarNotification oldNotification = oldEntry.notification;
 904
 905        // XXX: modify when we do something more intelligent with the two content views
 906        final RemoteViews oldContentView = oldNotification.getNotification().contentView;
 907        final RemoteViews contentView = notification.getNotification().contentView;
 908        final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
 909        final RemoteViews bigContentView = notification.getNotification().bigContentView;
 910
 911        if (DEBUG) {
 912            Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
 913                    + " ongoing=" + oldNotification.isOngoing()
 914                    + " expanded=" + oldEntry.expanded
 915                    + " contentView=" + oldContentView
 916                    + " bigContentView=" + oldBigContentView
 917                    + " rowParent=" + oldEntry.row.getParent());
 918            Log.d(TAG, "new notification: when=" + notification.getNotification().when
 919                    + " ongoing=" + oldNotification.isOngoing()
 920                    + " contentView=" + contentView
 921                    + " bigContentView=" + bigContentView);
 922        }
 923
 924        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
 925        // didn't change.
 926
 927        // 1U is never null
 928        boolean contentsUnchanged = oldEntry.expanded != null
 929                && contentView.getPackage() != null
 930                && oldContentView.getPackage() != null
 931                && oldContentView.getPackage().equals(contentView.getPackage())
 932                && oldContentView.getLayoutId() == contentView.getLayoutId();
 933        // large view may be null
 934        boolean bigContentsUnchanged =
 935                (oldEntry.getBigContentView() == null && bigContentView == null)
 936                || ((oldEntry.getBigContentView() != null && bigContentView != null)
 937                    && bigContentView.getPackage() != null
 938                    && oldBigContentView.getPackage() != null
 939                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
 940                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
 941        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
 942        boolean orderUnchanged = notification.getNotification().when== oldNotification.getNotification().when
 943                && notification.getScore() == oldNotification.getScore();
 944                // score now encompasses/supersedes isOngoing()
 945
 946        boolean updateTicker = notification.getNotification().tickerText != null
 947                && !TextUtils.equals(notification.getNotification().tickerText,
 948                        oldEntry.notification.getNotification().tickerText);
 949        boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
 950        if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
 951            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
 952            oldEntry.notification = notification;
 953            try {
 954                updateNotificationViews(oldEntry, notification);
 955
 956                if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
 957                        && oldNotification == mInterruptingNotificationEntry.notification) {
 958                    if (!shouldInterrupt(notification)) {
 959                        if (DEBUG) Log.d(TAG, "no longer interrupts!");
 960                        mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
 961                    } else {
 962                        if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
 963                        mInterruptingNotificationEntry.notification = notification;
 964                        updateNotificationViews(mInterruptingNotificationEntry, notification);
 965                    }
 966                }
 967
 968                // Update the icon.
 969                final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
 970                        notification.getUser(),
 971                        notification.getNotification().icon, notification.getNotification().iconLevel,
 972                        notification.getNotification().number,
 973                        notification.getNotification().tickerText);
 974                if (!oldEntry.icon.set(ic)) {
 975                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
 976                    return;
 977                }
 978                updateExpansionStates();
 979            }
 980            catch (RuntimeException e) {
 981                // It failed to add cleanly.  Log, and remove the view from the panel.
 982                Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
 983                removeNotificationViews(key);
 984                addNotificationViews(key, notification);
 985            }
 986        } else {
 987            if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
 988            if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
 989            if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
 990            if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
 991            final boolean wasExpanded = oldEntry.row.isUserExpanded();
 992            removeNotificationViews(key);
 993            addNotificationViews(key, notification);  // will also replace the heads up
 994            if (wasExpanded) {
 995                final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
 996                newEntry.row.setExpanded(true);
 997                newEntry.row.setUserExpanded(true);
 998            }
 999        }
1000
1001        // Update the veto button accordingly (and as a result, whether this row is
1002        // swipe-dismissable)
1003        updateNotificationVetoButton(oldEntry.row, notification);
1004
1005        // Is this for you?
1006        boolean isForCurrentUser = notificationIsForCurrentUser(notification);
1007        if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
1008
1009        // Restart the ticker if it's still running
1010        if (updateTicker && isForCurrentUser) {
1011            haltTicker();
1012            tick(key, notification, false);
1013        }
1014
1015        // Recalculate the position of the sliding windows and the titles.
1016        setAreThereNotifications();
1017        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1018    }
1019
1020    private void updateNotificationViews(NotificationData.Entry entry,
1021            StatusBarNotification notification) {
1022        final RemoteViews contentView = notification.getNotification().contentView;
1023        final RemoteViews bigContentView = notification.getNotification().bigContentView;
1024        // Reapply the RemoteViews
1025        contentView.reapply(mContext, entry.expanded, mOnClickHandler);
1026        if (bigContentView != null && entry.getBigContentView() != null) {
1027            bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler);
1028        }
1029        // update the contentIntent
1030        final PendingIntent contentIntent = notification.getNotification().contentIntent;
1031        if (contentIntent != null) {
1032            final View.OnClickListener listener = makeClicker(contentIntent,
1033                    notification.getPackageName(), notification.getTag(), notification.getId());
1034            entry.content.setOnClickListener(listener);
1035        } else {
1036            entry.content.setOnClickListener(null);
1037        }
1038    }
1039
1040    protected void notifyHeadsUpScreenOn(boolean screenOn) {
1041        if (!screenOn && mInterruptingNotificationEntry != null) {
1042            mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
1043        }
1044    }
1045
1046    protected boolean shouldInterrupt(StatusBarNotification sbn) {
1047        Notification notification = sbn.getNotification();
1048        // some predicates to make the boolean logic legible
1049        boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
1050                || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
1051                || notification.sound != null
1052                || notification.vibrate != null;
1053        boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
1054        boolean isFullscreen = notification.fullScreenIntent != null;
1055        boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
1056                Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
1057
1058        final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
1059        boolean interrupt = (isFullscreen || (isHighPriority && isNoisy))
1060                && isAllowed
1061                && mPowerManager.isScreenOn()
1062                && !keyguard.isShowingAndNotHidden()
1063                && !keyguard.isInputRestricted();
1064        try {
1065            interrupt = interrupt && !mDreamManager.isDreaming();
1066        } catch (RemoteException e) {
1067            Log.d(TAG, "failed to query dream manager", e);
1068        }
1069        if (DEBUG) Log.d(TAG, "interrupt: " + interrupt);
1070        return interrupt;
1071    }
1072
1073    // Q: What kinds of notifications should show during setup?
1074    // A: Almost none! Only things coming from the system (package is "android") that also
1075    // have special "kind" tags marking them as relevant for setup (see below).
1076    protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
1077        if ("android".equals(sbn.getPackageName())) {
1078            if (sbn.getNotification().kind != null) {
1079                for (String aKind : sbn.getNotification().kind) {
1080                    // IME switcher, created by InputMethodManagerService
1081                    if ("android.system.imeswitcher".equals(aKind)) return true;
1082                    // OTA availability & errors, created by SystemUpdateService
1083                    if ("android.system.update".equals(aKind)) return true;
1084                }
1085            }
1086        }
1087        return false;
1088    }
1089
1090    public boolean inKeyguardRestrictedInputMode() {
1091        return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
1092    }
1093
1094    public void setInteracting(int barWindow, boolean interacting) {
1095        // hook for subclasses
1096    }
1097
1098    public void destroy() {
1099        if (mSearchPanelView != null) {
1100            mWindowManager.removeViewImmediate(mSearchPanelView);
1101        }
1102        mContext.unregisterReceiver(mBroadcastReceiver);
1103    }
1104}