blob: 683824b5e468f51eff70028b084d797f0a88fb3e [file] [log] [blame]
Michael Jurkacb2522c2012-04-13 09:32:47 -07001
Joe Onorato2314aab2010-04-08 16:41:23 -05002/*
3 * Copyright (C) 2010 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
Joe Onorato79de0c52010-05-26 17:03:26 -040018package com.android.systemui.statusbar;
Joe Onorato2314aab2010-04-08 16:41:23 -050019
Daniel Sandler5feceeb2013-03-22 18:29:23 -070020import android.service.notification.StatusBarNotification;
Fabrice Di Meglio8afcd142012-07-27 18:27:11 -070021import android.content.res.Configuration;
Adam Powell0fc5b2b2012-07-18 18:20:29 -070022import com.android.internal.statusbar.IStatusBarService;
23import com.android.internal.statusbar.StatusBarIcon;
24import com.android.internal.statusbar.StatusBarIconList;
Adam Powell0fc5b2b2012-07-18 18:20:29 -070025import com.android.internal.widget.SizeAdaptiveLayout;
26import com.android.systemui.R;
27import com.android.systemui.SearchPanelView;
28import com.android.systemui.SystemUI;
29import com.android.systemui.recent.RecentTasksLoader;
Michael Jurkacb2522c2012-04-13 09:32:47 -070030import com.android.systemui.recent.RecentsActivity;
Adam Powell0fc5b2b2012-07-18 18:20:29 -070031import com.android.systemui.recent.TaskDescription;
32import com.android.systemui.statusbar.policy.NotificationRowLayout;
33import com.android.systemui.statusbar.tablet.StatusBarPanel;
34
Christopher Tate5e08af02012-09-21 17:17:22 -070035import android.app.ActivityManager;
Daniel Sandler6a858c32012-03-12 14:38:58 -040036import android.app.ActivityManagerNative;
Michael Jurkacb2522c2012-04-13 09:32:47 -070037import android.app.ActivityOptions;
Daniel Sandler6a858c32012-03-12 14:38:58 -040038import android.app.KeyguardManager;
39import android.app.PendingIntent;
Adam Powell0fc5b2b2012-07-18 18:20:29 -070040import android.app.TaskStackBuilder;
Michael Jurkacb2522c2012-04-13 09:32:47 -070041import android.content.ActivityNotFoundException;
Daniel Sandlerb9301c32012-08-14 15:08:24 -040042import android.content.BroadcastReceiver;
Joe Onorato808182d2010-07-09 18:52:06 -040043import android.content.Context;
Daniel Sandler6a858c32012-03-12 14:38:58 -040044import android.content.Intent;
Daniel Sandlerb9301c32012-08-14 15:08:24 -040045import android.content.IntentFilter;
Daniel Sandler96fd7c12012-03-30 16:37:36 -040046import android.content.pm.ApplicationInfo;
47import android.content.pm.PackageManager.NameNotFoundException;
Michael Jurkacb2522c2012-04-13 09:32:47 -070048import android.content.res.Resources;
Daniel Sandler26cda272012-05-22 15:44:08 -040049import android.database.ContentObserver;
Michael Jurkacb2522c2012-04-13 09:32:47 -070050import android.graphics.Bitmap;
51import android.graphics.Paint;
Daniel Sandler6a858c32012-03-12 14:38:58 -040052import android.graphics.Rect;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040053import android.net.Uri;
Daniel Sandler96fd7c12012-03-30 16:37:36 -040054import android.os.Build;
Michael Jurka7f2668c2012-03-27 07:49:52 -070055import android.os.Handler;
Joe Onorato808182d2010-07-09 18:52:06 -040056import android.os.IBinder;
Michael Jurka7f2668c2012-03-27 07:49:52 -070057import android.os.Message;
Joe Onorato808182d2010-07-09 18:52:06 -040058import android.os.RemoteException;
59import android.os.ServiceManager;
Dianne Hackbornf02b60a2012-08-16 10:48:27 -070060import android.os.UserHandle;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040061import android.provider.Settings;
Chris Wren0c8275b2012-05-08 13:36:48 -040062import android.text.TextUtils;
Michael Jurkacb2522c2012-04-13 09:32:47 -070063import android.util.DisplayMetrics;
Joe Onorato808182d2010-07-09 18:52:06 -040064import android.util.Log;
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -050065import android.util.Slog;
Daniel Sandlerc638c1e2011-08-24 16:19:23 -070066import android.view.Display;
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -050067import android.view.IWindowManager;
Michael Jurka7f2668c2012-03-27 07:49:52 -070068import android.view.LayoutInflater;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040069import android.view.MenuItem;
Michael Jurka7f2668c2012-03-27 07:49:52 -070070import android.view.MotionEvent;
Joe Onorato808182d2010-07-09 18:52:06 -040071import android.view.View;
Daniel Sandler6a858c32012-03-12 14:38:58 -040072import android.view.ViewGroup;
Michael Jurka7f2668c2012-03-27 07:49:52 -070073import android.view.ViewGroup.LayoutParams;
Joe Onorato808182d2010-07-09 18:52:06 -040074import android.view.WindowManager;
Michael Jurka80343f62012-10-18 13:13:46 +020075import android.view.WindowManagerGlobal;
Chris Wren0c8275b2012-05-08 13:36:48 -040076import android.widget.ImageView;
Michael Jurka7f2668c2012-03-27 07:49:52 -070077import android.widget.LinearLayout;
Daniel Sandlerf7a19562012-04-04 14:04:21 -040078import android.widget.PopupMenu;
Michael Jurkaa600fd92012-06-25 15:57:05 -070079import android.widget.RemoteViews;
Daniel Sandlerb9301c32012-08-14 15:08:24 -040080import android.widget.TextView;
Joe Onorato808182d2010-07-09 18:52:06 -040081
Michael Jurkaa600fd92012-06-25 15:57:05 -070082import java.util.ArrayList;
Fabrice Di Meglio8afcd142012-07-27 18:27:11 -070083import java.util.Locale;
Joe Onorato1c95ecb2010-06-28 17:19:12 -040084
Michael Jurka7f2668c2012-03-27 07:49:52 -070085public abstract class BaseStatusBar extends SystemUI implements
Michael Jurkacb2522c2012-04-13 09:32:47 -070086 CommandQueue.Callbacks {
Daniel Sandler198a0302012-08-17 16:04:31 -040087 public static final String TAG = "StatusBar";
88 public static final boolean DEBUG = false;
Daniel Sandlerb9301c32012-08-14 15:08:24 -040089 public static final boolean MULTIUSER_DEBUG = false;
Joe Onorato1c95ecb2010-06-28 17:19:12 -040090
Michael Jurkacb2522c2012-04-13 09:32:47 -070091 protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
Michael Jurka7f2668c2012-03-27 07:49:52 -070092 protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
93 protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
94 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
Jim Millere898ac52012-04-06 17:10:57 -070095 protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
96 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
Chris Wren0c8275b2012-05-08 13:36:48 -040097 protected static final int MSG_SHOW_INTRUDER = 1026;
98 protected static final int MSG_HIDE_INTRUDER = 1027;
99
100 protected static final boolean ENABLE_INTRUDERS = false;
101
Michael Jurka56a57832012-05-14 13:24:43 -0700102 // Should match the value in PhoneWindowManager
103 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
104
Chris Wren0c8275b2012-05-08 13:36:48 -0400105 public static final int EXPANDED_LEAVE_ALONE = -10000;
106 public static final int EXPANDED_FULL_OPEN = -10001;
Michael Jurka7f2668c2012-03-27 07:49:52 -0700107
Joe Onorato808182d2010-07-09 18:52:06 -0400108 protected CommandQueue mCommandQueue;
109 protected IStatusBarService mBarService;
Michael Jurka7f2668c2012-03-27 07:49:52 -0700110 protected H mHandler = createHandler();
111
Chris Wren0c8275b2012-05-08 13:36:48 -0400112 // all notifications
113 protected NotificationData mNotificationData = new NotificationData();
114 protected NotificationRowLayout mPile;
115
116 protected StatusBarNotification mCurrentlyIntrudingNotification;
117
Daniel Sandler6a858c32012-03-12 14:38:58 -0400118 // used to notify status bar for suppressing notification LED
119 protected boolean mPanelSlightlyVisible;
120
Jim Millere898ac52012-04-06 17:10:57 -0700121 // Search panel
122 protected SearchPanelView mSearchPanelView;
123
Daniel Sandler469e96e2012-05-04 15:56:19 -0400124 protected PopupMenu mNotificationBlamePopup;
125
Daniel Sandlerb9301c32012-08-14 15:08:24 -0400126 protected int mCurrentUserId = 0;
127
Fabrice Di Meglio8afcd142012-07-27 18:27:11 -0700128 protected int mLayoutDirection;
129 private Locale mLocale;
130
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500131 // UI-specific methods
Daniel Sandler6a858c32012-03-12 14:38:58 -0400132
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500133 /**
134 * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
135 * and add them to the window manager.
136 */
137 protected abstract void createAndAddWindows();
Joe Onorato1c95ecb2010-06-28 17:19:12 -0400138
Jeff Brown98365d72012-08-19 20:30:52 -0700139 protected WindowManager mWindowManager;
140 protected IWindowManager mWindowManagerService;
Fabrice Di Meglio8afcd142012-07-27 18:27:11 -0700141 protected abstract void refreshLayout(int layoutDirection);
142
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500143 protected Display mDisplay;
Jeff Brown98365d72012-08-19 20:30:52 -0700144
Daniel Sandler26cda272012-05-22 15:44:08 -0400145 private boolean mDeviceProvisioned = false;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400146
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500147 public IStatusBarService getStatusBarService() {
148 return mBarService;
149 }
Joe Onorato2039e482010-11-29 14:54:24 -0800150
Daniel Sandler3ffdcc72012-09-23 14:31:48 -0400151 public boolean isDeviceProvisioned() {
Daniel Sandler26cda272012-05-22 15:44:08 -0400152 return mDeviceProvisioned;
153 }
154
155 private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
156 @Override
157 public void onChange(boolean selfChange) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700158 final boolean provisioned = 0 != Settings.Global.getInt(
159 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
Daniel Sandler26cda272012-05-22 15:44:08 -0400160 if (provisioned != mDeviceProvisioned) {
161 mDeviceProvisioned = provisioned;
162 updateNotificationIcons();
163 }
164 }
165 };
166
Dianne Hackborn1927ae82012-06-22 15:21:36 -0700167 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
168 @Override
169 public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
Daniel Sandler198a0302012-08-17 16:04:31 -0400170 if (DEBUG) {
171 Slog.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
172 }
Dianne Hackborn1927ae82012-06-22 15:21:36 -0700173 final boolean isActivity = pendingIntent.isActivity();
174 if (isActivity) {
175 try {
176 // The intent we are sending is for the application, which
177 // won't have permission to immediately start an activity after
178 // the user switches to home. We know it is safe to do at this
179 // point, so make sure new activity switches are now allowed.
180 ActivityManagerNative.getDefault().resumeAppSwitches();
181 // Also, notifications can be launched from the lock screen,
182 // so dismiss the lock screen when the activity starts.
183 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
184 } catch (RemoteException e) {
185 }
186 }
187
188 boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
189
190 if (isActivity && handled) {
191 // close the shade if it was open
Daniel Sandler11cf1782012-09-27 14:03:08 -0400192 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
Dianne Hackborn1927ae82012-06-22 15:21:36 -0700193 visibilityChanged(false);
194 }
195 return handled;
196 }
197 };
198
Joe Onoratof3c3c4f2010-10-21 11:09:02 -0400199 public void start() {
Jeff Brown98365d72012-08-19 20:30:52 -0700200 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
201 mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
202 mDisplay = mWindowManager.getDefaultDisplay();
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500203
Daniel Sandler26cda272012-05-22 15:44:08 -0400204 mProvisioningObserver.onChange(false); // set up
205 mContext.getContentResolver().registerContentObserver(
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700206 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
Daniel Sandler26cda272012-05-22 15:44:08 -0400207 mProvisioningObserver);
208
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500209 mBarService = IStatusBarService.Stub.asInterface(
210 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Joe Onorato1c95ecb2010-06-28 17:19:12 -0400211
Fabrice Di Meglio8afcd142012-07-27 18:27:11 -0700212 mLocale = mContext.getResources().getConfiguration().locale;
213 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
214
Joe Onorato2314aab2010-04-08 16:41:23 -0500215 // Connect in to the status bar manager service
Joe Onorato0cbda992010-05-02 16:28:15 -0700216 StatusBarIconList iconList = new StatusBarIconList();
Joe Onorato75199e32010-05-29 17:22:51 -0400217 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
218 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
Joe Onorato66d7d012010-05-14 10:05:10 -0700219 mCommandQueue = new CommandQueue(this, iconList);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400220
Jeff Brown2992ea72011-01-28 22:04:14 -0800221 int[] switches = new int[7];
satokcd7cd292010-11-20 15:46:23 +0900222 ArrayList<IBinder> binders = new ArrayList<IBinder>();
Joe Onorato2314aab2010-04-08 16:41:23 -0500223 try {
Joe Onorato93056472010-09-10 10:30:46 -0400224 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
satokcd7cd292010-11-20 15:46:23 +0900225 switches, binders);
Joe Onorato2314aab2010-04-08 16:41:23 -0500226 } catch (RemoteException ex) {
227 // If the system process isn't there we're doomed anyway.
228 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400229
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500230 createAndAddWindows();
Joe Onorato0cbda992010-05-02 16:28:15 -0700231
Joe Onoratoe4c7b3f2010-10-30 12:15:03 -0700232 disable(switches[0]);
Dianne Hackborn3a3a6cf2012-03-26 10:24:04 -0700233 setSystemUiVisibility(switches[1], 0xffffffff);
Dianne Hackborn7d049322011-06-14 15:00:32 -0700234 topAppWindowChanged(switches[2] != 0);
satokcd7cd292010-11-20 15:46:23 +0900235 // StatusBarManagerService has a back up of IME token and it's restored here.
Joe Onorato857fd9b2011-01-27 15:08:35 -0800236 setImeWindowStatus(binders.get(0), switches[3], switches[4]);
Jeff Brown2992ea72011-01-28 22:04:14 -0800237 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
Joe Onorato93056472010-09-10 10:30:46 -0400238
Joe Onorato0cbda992010-05-02 16:28:15 -0700239 // Set up the initial icon state
Joe Onorato75199e32010-05-29 17:22:51 -0400240 int N = iconList.size();
Joe Onorato0cbda992010-05-02 16:28:15 -0700241 int viewIndex = 0;
242 for (int i=0; i<N; i++) {
243 StatusBarIcon icon = iconList.getIcon(i);
244 if (icon != null) {
245 addIcon(iconList.getSlot(i), i, viewIndex, icon);
246 viewIndex++;
247 }
248 }
249
Joe Onorato75199e32010-05-29 17:22:51 -0400250 // Set up the initial notification state
251 N = notificationKeys.size();
Joe Onorato75199e32010-05-29 17:22:51 -0400252 if (N == notifications.size()) {
253 for (int i=0; i<N; i++) {
254 addNotification(notificationKeys.get(i), notifications.get(i));
255 }
256 } else {
257 Log.wtf(TAG, "Notification list length mismatch: keys=" + N
258 + " notifications=" + notifications.size());
259 }
260
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500261 if (DEBUG) {
262 Slog.d(TAG, String.format(
Daniel Sandler6a858c32012-03-12 14:38:58 -0400263 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
Daniel Sandlerc6d29fc2012-02-25 00:33:12 -0500264 iconList.size(),
265 switches[0],
266 switches[1],
267 switches[2],
268 switches[3]
269 ));
Joe Onorato1c95ecb2010-06-28 17:19:12 -0400270 }
Daniel Sandlerb9301c32012-08-14 15:08:24 -0400271
Christopher Tate5e08af02012-09-21 17:17:22 -0700272 mCurrentUserId = ActivityManager.getCurrentUser();
Daniel Sandlerb9301c32012-08-14 15:08:24 -0400273
274 IntentFilter filter = new IntentFilter();
275 filter.addAction(Intent.ACTION_USER_SWITCHED);
276 mContext.registerReceiver(new BroadcastReceiver() {
277 @Override
278 public void onReceive(Context context, Intent intent) {
279 String action = intent.getAction();
280 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
Amith Yamasani2a003292012-08-14 18:25:45 -0700281 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
Daniel Sandlerb9301c32012-08-14 15:08:24 -0400282 if (true) Slog.v(TAG, "userId " + mCurrentUserId + " is in the house");
283 userSwitched(mCurrentUserId);
284 }
285 }}, filter);
Fabrice Di Meglio8afcd142012-07-27 18:27:11 -0700286
287 mLocale = mContext.getResources().getConfiguration().locale;
Daniel Sandlerb9301c32012-08-14 15:08:24 -0400288 }
289
290 public void userSwitched(int newUserId) {
291 // should be overridden
292 }
293
294 public boolean notificationIsForCurrentUser(StatusBarNotification n) {
295 final int thisUserId = mCurrentUserId;
296 final int notificationUserId = n.getUserId();
297 if (DEBUG && MULTIUSER_DEBUG) {
298 Slog.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
299 n, thisUserId, notificationUserId));
300 }
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700301 return notificationUserId == UserHandle.USER_ALL
302 || thisUserId == notificationUserId;
Joe Onorato2314aab2010-04-08 16:41:23 -0500303 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400304
Fabrice Di Meglio8afcd142012-07-27 18:27:11 -0700305 @Override
306 protected void onConfigurationChanged(Configuration newConfig) {
307 final Locale newLocale = mContext.getResources().getConfiguration().locale;
308 if (! newLocale.equals(mLocale)) {
309 mLocale = newLocale;
310 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
311 refreshLayout(mLayoutDirection);
312 }
313 }
314
Winson Chungc57ccf02011-10-13 15:04:59 -0700315 protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
316 View vetoButton = row.findViewById(R.id.veto);
317 if (n.isClearable()) {
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400318 final String _pkg = n.getPackageName();
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400319 final String _tag = n.getTag();
320 final int _id = n.getId();
Winson Chungc57ccf02011-10-13 15:04:59 -0700321 vetoButton.setOnClickListener(new View.OnClickListener() {
322 public void onClick(View v) {
Casey Burkhardtbac221f2012-10-03 18:13:58 -0700323 // Accessibility feedback
324 v.announceForAccessibility(
325 mContext.getString(R.string.accessibility_notification_dismissed));
Winson Chungc57ccf02011-10-13 15:04:59 -0700326 try {
327 mBarService.onNotificationClear(_pkg, _tag, _id);
Casey Burkhardtbac221f2012-10-03 18:13:58 -0700328
Winson Chungc57ccf02011-10-13 15:04:59 -0700329 } catch (RemoteException ex) {
330 // system process is dead if we're here.
331 }
332 }
333 });
334 vetoButton.setVisibility(View.VISIBLE);
335 } else {
336 vetoButton.setVisibility(View.GONE);
337 }
Casey Burkhardtbac221f2012-10-03 18:13:58 -0700338 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
Winson Chungc57ccf02011-10-13 15:04:59 -0700339 return vetoButton;
340 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400341
Daniel Sandler96fd7c12012-03-30 16:37:36 -0400342
343 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400344 if (sbn.getNotification().contentView.getLayoutId() !=
Daniel Sandler96fd7c12012-03-30 16:37:36 -0400345 com.android.internal.R.layout.notification_template_base) {
346 int version = 0;
347 try {
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400348 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
Daniel Sandler96fd7c12012-03-30 16:37:36 -0400349 version = info.targetSdkVersion;
350 } catch (NameNotFoundException ex) {
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400351 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
Daniel Sandler96fd7c12012-03-30 16:37:36 -0400352 }
353 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
354 content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
355 } else {
Chris Wrend84e5932012-04-19 17:58:03 -0400356 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
Daniel Sandler96fd7c12012-03-30 16:37:36 -0400357 }
358 }
359 }
360
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400361 private void startApplicationDetailsActivity(String packageName) {
362 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
363 Uri.fromParts("package", packageName, null));
Adam Powell0fc5b2b2012-07-18 18:20:29 -0700364 intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
Amith Yamasaniea7e9152012-09-24 16:11:18 -0700365 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
366 null, UserHandle.CURRENT);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400367 }
368
Jim Miller9a720f52012-05-30 03:19:43 -0700369 protected View.OnLongClickListener getNotificationLongClicker() {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400370 return new View.OnLongClickListener() {
371 @Override
372 public boolean onLongClick(View v) {
373 final String packageNameF = (String) v.getTag();
374 if (packageNameF == null) return false;
Jeff Sharkeyaf232ed2012-04-30 15:19:39 -0700375 if (v.getWindowToken() == null) return false;
Daniel Sandler469e96e2012-05-04 15:56:19 -0400376 mNotificationBlamePopup = new PopupMenu(mContext, v);
377 mNotificationBlamePopup.getMenuInflater().inflate(
378 R.menu.notification_popup_menu,
379 mNotificationBlamePopup.getMenu());
380 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400381 public boolean onMenuItemClick(MenuItem item) {
382 if (item.getItemId() == R.id.notification_inspect_item) {
383 startApplicationDetailsActivity(packageNameF);
Daniel Sandler11cf1782012-09-27 14:03:08 -0400384 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400385 } else {
386 return false;
387 }
388 return true;
389 }
390 });
Daniel Sandler469e96e2012-05-04 15:56:19 -0400391 mNotificationBlamePopup.show();
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400392
393 return true;
394 }
395 };
396 }
397
Daniel Sandler469e96e2012-05-04 15:56:19 -0400398 public void dismissPopups() {
399 if (mNotificationBlamePopup != null) {
400 mNotificationBlamePopup.dismiss();
401 mNotificationBlamePopup = null;
402 }
403 }
404
Daniel Sandlerfa7887b2012-03-26 09:43:31 -0400405 public void dismissIntruder() {
406 // pass
407 }
Michael Jurka7f2668c2012-03-27 07:49:52 -0700408
409 @Override
410 public void toggleRecentApps() {
Michael Jurkacb2522c2012-04-13 09:32:47 -0700411 int msg = MSG_TOGGLE_RECENTS_PANEL;
Michael Jurka7f2668c2012-03-27 07:49:52 -0700412 mHandler.removeMessages(msg);
413 mHandler.sendEmptyMessage(msg);
414 }
415
416 @Override
417 public void preloadRecentApps() {
418 int msg = MSG_PRELOAD_RECENT_APPS;
419 mHandler.removeMessages(msg);
420 mHandler.sendEmptyMessage(msg);
421 }
422
423 @Override
424 public void cancelPreloadRecentApps() {
425 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
426 mHandler.removeMessages(msg);
427 mHandler.sendEmptyMessage(msg);
428 }
429
430 @Override
Jim Millere898ac52012-04-06 17:10:57 -0700431 public void showSearchPanel() {
432 int msg = MSG_OPEN_SEARCH_PANEL;
433 mHandler.removeMessages(msg);
434 mHandler.sendEmptyMessage(msg);
435 }
436
437 @Override
438 public void hideSearchPanel() {
439 int msg = MSG_CLOSE_SEARCH_PANEL;
440 mHandler.removeMessages(msg);
441 mHandler.sendEmptyMessage(msg);
442 }
443
Michael Jurka7f2668c2012-03-27 07:49:52 -0700444 protected abstract WindowManager.LayoutParams getRecentsLayoutParams(
445 LayoutParams layoutParams);
446
Jim Millere898ac52012-04-06 17:10:57 -0700447 protected abstract WindowManager.LayoutParams getSearchLayoutParams(
448 LayoutParams layoutParams);
449
Jim Millere898ac52012-04-06 17:10:57 -0700450 protected void updateSearchPanel() {
451 // Search Panel
452 boolean visible = false;
453 if (mSearchPanelView != null) {
454 visible = mSearchPanelView.isShowing();
Jeff Brown98365d72012-08-19 20:30:52 -0700455 mWindowManager.removeView(mSearchPanelView);
Jim Millere898ac52012-04-06 17:10:57 -0700456 }
457
458 // Provide SearchPanel with a temporary parent to allow layout params to work.
459 LinearLayout tmpRoot = new LinearLayout(mContext);
460 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
461 R.layout.status_bar_search_panel, tmpRoot, false);
462 mSearchPanelView.setOnTouchListener(
463 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
464 mSearchPanelView.setVisibility(View.GONE);
465
466 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
467
Jeff Brown98365d72012-08-19 20:30:52 -0700468 mWindowManager.addView(mSearchPanelView, lp);
Jim Millere898ac52012-04-06 17:10:57 -0700469 mSearchPanelView.setBar(this);
470 if (visible) {
471 mSearchPanelView.show(true, false);
472 }
473 }
474
Michael Jurkaecc395a2012-03-30 05:31:46 -0700475 protected H createHandler() {
Michael Jurka7f2668c2012-03-27 07:49:52 -0700476 return new H();
477 }
478
Michael Jurka56a57832012-05-14 13:24:43 -0700479 static void sendCloseSystemWindows(Context context, String reason) {
480 if (ActivityManagerNative.isSystemReady()) {
481 try {
482 ActivityManagerNative.getDefault().closeSystemDialogs(reason);
483 } catch (RemoteException e) {
484 }
485 }
486 }
487
Michael Jurkacb2522c2012-04-13 09:32:47 -0700488 protected abstract View getStatusBarView();
489
490 protected void toggleRecentsActivity() {
491 try {
Michael Jurka80343f62012-10-18 13:13:46 +0200492
493 TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask();
Michael Jurkacb2522c2012-04-13 09:32:47 -0700494
495 Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT);
496 intent.setClassName("com.android.systemui",
497 "com.android.systemui.recent.RecentsActivity");
498 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
499 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
500
501 if (firstTask == null) {
Michael Jurka833808d2012-10-19 19:12:29 +0200502 if (RecentsActivity.forceOpaqueBackground(mContext)) {
503 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
504 R.anim.recents_launch_from_launcher_enter,
505 R.anim.recents_launch_from_launcher_exit);
506 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
507 UserHandle.USER_CURRENT));
508 } else {
509 // The correct window animation will be applied via the activity's style
510 mContext.startActivityAsUser(intent, new UserHandle(
511 UserHandle.USER_CURRENT));
512 }
513
Michael Jurkacb2522c2012-04-13 09:32:47 -0700514 } else {
515 Bitmap first = firstTask.getThumbnail();
516 final Resources res = mContext.getResources();
517
518 float thumbWidth = res
519 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width);
520 float thumbHeight = res
521 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height);
Michael Jurkaa4dd3012012-09-04 03:49:04 -0700522 if (first == null) {
523 throw new RuntimeException("Recents thumbnail is null");
524 }
Michael Jurkacb2522c2012-04-13 09:32:47 -0700525 if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) {
526 first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight,
527 true);
Michael Jurkaa4dd3012012-09-04 03:49:04 -0700528 if (first == null) {
529 throw new RuntimeException("Recents thumbnail is null");
530 }
Michael Jurkacb2522c2012-04-13 09:32:47 -0700531 }
532
Michael Jurkaa4dd3012012-09-04 03:49:04 -0700533
Michael Jurkacb2522c2012-04-13 09:32:47 -0700534 DisplayMetrics dm = new DisplayMetrics();
535 mDisplay.getMetrics(dm);
536 // calculate it here, but consider moving it elsewhere
537 // first, determine which orientation you're in.
538 // todo: move the system_bar layouts to sw600dp ?
539 final Configuration config = res.getConfiguration();
540 int x, y;
541
542 if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
543 float appLabelLeftMargin = res
544 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_left_margin);
545 float appLabelWidth = res
546 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_width);
547 float thumbLeftMargin = res
548 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_left_margin);
549 float thumbBgPadding = res
550 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_bg_padding);
551
552 float width = appLabelLeftMargin +
553 +appLabelWidth
554 + thumbLeftMargin
555 + thumbWidth
556 + 2 * thumbBgPadding;
557
558 x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth
559 + thumbBgPadding + thumbLeftMargin);
560 y = (int) (dm.heightPixels
561 - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height) - thumbBgPadding);
Michael Jurkae9c90272013-03-06 16:50:48 +0100562 if (mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
563 x = dm.widthPixels - x - res
564 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width);
565 }
566
Michael Jurkaa4dd3012012-09-04 03:49:04 -0700567 } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Michael Jurkacb2522c2012-04-13 09:32:47 -0700568 float thumbTopMargin = res
569 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_top_margin);
570 float thumbBgPadding = res
571 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_bg_padding);
572 float textPadding = res
573 .getDimensionPixelSize(R.dimen.status_bar_recents_text_description_padding);
574 float labelTextSize = res
575 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_text_size);
576 Paint p = new Paint();
577 p.setTextSize(labelTextSize);
578 float labelTextHeight = p.getFontMetricsInt().bottom
579 - p.getFontMetricsInt().top;
580 float descriptionTextSize = res
581 .getDimensionPixelSize(R.dimen.status_bar_recents_app_description_text_size);
Michael Jurka9bdaada2012-10-01 13:58:29 +0200582 p.setTextSize(descriptionTextSize);
Michael Jurkacb2522c2012-04-13 09:32:47 -0700583 float descriptionTextHeight = p.getFontMetricsInt().bottom
584 - p.getFontMetricsInt().top;
585
586 float statusBarHeight = res
587 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
588 float recentsItemTopPadding = statusBarHeight;
589
590 float height = thumbTopMargin
591 + thumbHeight
592 + 2 * thumbBgPadding + textPadding + labelTextHeight
593 + recentsItemTopPadding + textPadding + descriptionTextHeight;
594 float recentsItemRightPadding = res
595 .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding);
596 float recentsScrollViewRightPadding = res
597 .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin);
598 x = (int) (dm.widthPixels - res
599 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width)
600 - thumbBgPadding - recentsItemRightPadding - recentsScrollViewRightPadding);
601 y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin
602 + recentsItemTopPadding + thumbBgPadding + statusBarHeight);
603 }
604
605 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(
606 getStatusBarView(),
607 first, x, y,
Michael Jurka9bdaada2012-10-01 13:58:29 +0200608 new ActivityOptions.OnAnimationStartedListener() {
609 public void onAnimationStarted() {
Michael Jurka80343f62012-10-18 13:13:46 +0200610 Intent intent = new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT);
611 intent.setPackage("com.android.systemui");
612 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
Michael Jurka9bdaada2012-10-01 13:58:29 +0200613 }
614 });
Michael Jurka80343f62012-10-18 13:13:46 +0200615 intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true);
Michael Jurkacb2522c2012-04-13 09:32:47 -0700616 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
617 UserHandle.USER_CURRENT));
618 }
619 return;
620 } catch (ActivityNotFoundException e) {
621 Log.e(TAG, "Failed to launch RecentAppsIntent", e);
622 }
623 }
624
Michael Jurka80343f62012-10-18 13:13:46 +0200625 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
626 // additional optimization when we have software system buttons - start loading the recent
627 // tasks on touch down
628 @Override
629 public boolean onTouch(View v, MotionEvent event) {
630 int action = event.getAction() & MotionEvent.ACTION_MASK;
631 if (action == MotionEvent.ACTION_DOWN) {
632 preloadRecentTasksList();
633 } else if (action == MotionEvent.ACTION_CANCEL) {
634 cancelPreloadingRecentTasksList();
635 } else if (action == MotionEvent.ACTION_UP) {
636 if (!v.isPressed()) {
637 cancelPreloadingRecentTasksList();
638 }
639
640 }
641 return false;
642 }
643 };
644
645 protected void preloadRecentTasksList() {
646 if (DEBUG) Slog.d(TAG, "preloading recents");
647 Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
648 intent.setClassName("com.android.systemui",
649 "com.android.systemui.recent.RecentsPreloadReceiver");
650 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
651
652 RecentTasksLoader.getInstance(mContext).preloadFirstTask();
653 }
654
655 protected void cancelPreloadingRecentTasksList() {
656 if (DEBUG) Slog.d(TAG, "cancel preloading recents");
657 Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
658 intent.setClassName("com.android.systemui",
659 "com.android.systemui.recent.RecentsPreloadReceiver");
660 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
661
662 RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
663 }
664
Michael Jurka7f2668c2012-03-27 07:49:52 -0700665 protected class H extends Handler {
666 public void handleMessage(Message m) {
Michael Jurka80343f62012-10-18 13:13:46 +0200667 Intent intent;
Michael Jurka7f2668c2012-03-27 07:49:52 -0700668 switch (m.what) {
Michael Jurkacb2522c2012-04-13 09:32:47 -0700669 case MSG_TOGGLE_RECENTS_PANEL:
670 if (DEBUG) Slog.d(TAG, "toggle recents panel");
671 toggleRecentsActivity();
672 break;
Michael Jurka7f2668c2012-03-27 07:49:52 -0700673 case MSG_CLOSE_RECENTS_PANEL:
Michael Jurkacb2522c2012-04-13 09:32:47 -0700674 if (DEBUG) Slog.d(TAG, "closing recents panel");
Michael Jurka80343f62012-10-18 13:13:46 +0200675 intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
Michael Jurkacb2522c2012-04-13 09:32:47 -0700676 intent.setPackage("com.android.systemui");
677 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
678 break;
Michael Jurka7f2668c2012-03-27 07:49:52 -0700679 case MSG_PRELOAD_RECENT_APPS:
Michael Jurka80343f62012-10-18 13:13:46 +0200680 preloadRecentTasksList();
Michael Jurka7f2668c2012-03-27 07:49:52 -0700681 break;
682 case MSG_CANCEL_PRELOAD_RECENT_APPS:
Michael Jurka80343f62012-10-18 13:13:46 +0200683 cancelPreloadingRecentTasksList();
Michael Jurka7f2668c2012-03-27 07:49:52 -0700684 break;
Jim Millere898ac52012-04-06 17:10:57 -0700685 case MSG_OPEN_SEARCH_PANEL:
686 if (DEBUG) Slog.d(TAG, "opening search panel");
Jim Miller07994402012-05-02 14:22:27 -0700687 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
Jim Millere898ac52012-04-06 17:10:57 -0700688 mSearchPanelView.show(true, true);
689 }
690 break;
691 case MSG_CLOSE_SEARCH_PANEL:
692 if (DEBUG) Slog.d(TAG, "closing search panel");
693 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
694 mSearchPanelView.show(false, true);
695 }
696 break;
Michael Jurka7f2668c2012-03-27 07:49:52 -0700697 }
698 }
699 }
700
701 public class TouchOutsideListener implements View.OnTouchListener {
702 private int mMsg;
703 private StatusBarPanel mPanel;
704
705 public TouchOutsideListener(int msg, StatusBarPanel panel) {
706 mMsg = msg;
707 mPanel = panel;
708 }
709
710 public boolean onTouch(View v, MotionEvent ev) {
711 final int action = ev.getAction();
712 if (action == MotionEvent.ACTION_OUTSIDE
713 || (action == MotionEvent.ACTION_DOWN
714 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
715 mHandler.removeMessages(mMsg);
716 mHandler.sendEmptyMessage(mMsg);
717 return true;
718 }
719 return false;
720 }
721 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400722
723 protected void workAroundBadLayerDrawableOpacity(View v) {
724 }
725
726 protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
727 int minHeight =
728 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
729 int maxHeight =
730 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
731 StatusBarNotification sbn = entry.notification;
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400732 RemoteViews oneU = sbn.getNotification().contentView;
733 RemoteViews large = sbn.getNotification().bigContentView;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400734 if (oneU == null) {
735 return false;
736 }
737
738 // create the row view
739 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
740 Context.LAYOUT_INFLATER_SERVICE);
741 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
Daniel Sandlerbc5559f2012-04-19 01:08:15 -0400742
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400743 // for blaming (see SwipeHelper.setLongPressListener)
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400744 row.setTag(sbn.getPackageName());
Daniel Sandlerf7a19562012-04-04 14:04:21 -0400745
Daniel Sandler6a858c32012-03-12 14:38:58 -0400746 workAroundBadLayerDrawableOpacity(row);
747 View vetoButton = updateNotificationVetoButton(row, sbn);
748 vetoButton.setContentDescription(mContext.getString(
749 R.string.accessibility_remove_notification));
750
751 // NB: the large icon is now handled entirely by the template
752
753 // bind the click event to the content area
754 ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
755 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
Daniel Sandlerbc5559f2012-04-19 01:08:15 -0400756
Daniel Sandler6a858c32012-03-12 14:38:58 -0400757 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
Daniel Sandlerbc5559f2012-04-19 01:08:15 -0400758
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400759 PendingIntent contentIntent = sbn.getNotification().contentIntent;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400760 if (contentIntent != null) {
761 final View.OnClickListener listener = new NotificationClicker(contentIntent,
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400762 sbn.getPackageName(), sbn.getTag(), sbn.getId());
Daniel Sandler6a858c32012-03-12 14:38:58 -0400763 content.setOnClickListener(listener);
764 } else {
765 content.setOnClickListener(null);
766 }
767
Chris Wrene9e37722012-05-11 17:40:14 -0400768 // TODO(cwren) normalize variable names with those in updateNotification
Daniel Sandler6a858c32012-03-12 14:38:58 -0400769 View expandedOneU = null;
770 View expandedLarge = null;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400771 try {
Dianne Hackborna1940212012-06-28 16:07:22 -0700772 expandedOneU = oneU.apply(mContext, adaptive, mOnClickHandler);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400773 if (large != null) {
Dianne Hackborna1940212012-06-28 16:07:22 -0700774 expandedLarge = large.apply(mContext, adaptive, mOnClickHandler);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400775 }
776 }
777 catch (RuntimeException e) {
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400778 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
Daniel Sandler8680bf82012-05-15 16:52:52 -0400779 Slog.e(TAG, "couldn't inflate view for notification " + ident, e);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400780 return false;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400781 }
782
Daniel Sandler8680bf82012-05-15 16:52:52 -0400783 if (expandedOneU != null) {
784 SizeAdaptiveLayout.LayoutParams params =
785 new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams());
Chris Wrened5cc0e2012-06-21 11:25:58 -0400786 params.minHeight = minHeight;
787 params.maxHeight = minHeight;
Daniel Sandler8680bf82012-05-15 16:52:52 -0400788 adaptive.addView(expandedOneU, params);
789 }
790 if (expandedLarge != null) {
791 SizeAdaptiveLayout.LayoutParams params =
792 new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams());
Chris Wrened5cc0e2012-06-21 11:25:58 -0400793 params.minHeight = minHeight+1;
Daniel Sandler8680bf82012-05-15 16:52:52 -0400794 params.maxHeight = maxHeight;
795 adaptive.addView(expandedLarge, params);
796 }
797 row.setDrawingCacheEnabled(true);
798
Daniel Sandler6a858c32012-03-12 14:38:58 -0400799 applyLegacyRowBackground(sbn, content);
800
Chris Wren8fd12652012-05-09 21:25:57 -0400801 row.setTag(R.id.expandable_tag, Boolean.valueOf(large != null));
Daniel Sandlerb9301c32012-08-14 15:08:24 -0400802
803 if (MULTIUSER_DEBUG) {
804 TextView debug = (TextView) row.findViewById(R.id.debug_info);
805 if (debug != null) {
806 debug.setVisibility(View.VISIBLE);
807 debug.setText("U " + entry.notification.getUserId());
808 }
809 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400810 entry.row = row;
811 entry.content = content;
812 entry.expanded = expandedOneU;
Chris Wren8fd12652012-05-09 21:25:57 -0400813 entry.setLargeView(expandedLarge);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400814
815 return true;
816 }
817
818 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
819 return new NotificationClicker(intent, pkg, tag, id);
820 }
821
822 private class NotificationClicker implements View.OnClickListener {
823 private PendingIntent mIntent;
824 private String mPkg;
825 private String mTag;
826 private int mId;
827
828 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
829 mIntent = intent;
830 mPkg = pkg;
831 mTag = tag;
832 mId = id;
833 }
834
835 public void onClick(View v) {
836 try {
837 // The intent we are sending is for the application, which
838 // won't have permission to immediately start an activity after
839 // the user switches to home. We know it is safe to do at this
840 // point, so make sure new activity switches are now allowed.
841 ActivityManagerNative.getDefault().resumeAppSwitches();
842 // Also, notifications can be launched from the lock screen,
843 // so dismiss the lock screen when the activity starts.
844 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
845 } catch (RemoteException e) {
846 }
847
848 if (mIntent != null) {
849 int[] pos = new int[2];
850 v.getLocationOnScreen(pos);
851 Intent overlay = new Intent();
852 overlay.setSourceBounds(
853 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
854 try {
855 mIntent.send(mContext, 0, overlay);
856 } catch (PendingIntent.CanceledException e) {
857 // the stack trace isn't very helpful here. Just log the exception message.
858 Slog.w(TAG, "Sending contentIntent failed: " + e);
859 }
860
861 KeyguardManager kgm =
862 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
863 if (kgm != null) kgm.exitKeyguardSecurely(null);
864 }
865
866 try {
867 mBarService.onNotificationClick(mPkg, mTag, mId);
868 } catch (RemoteException ex) {
869 // system process is dead if we're here.
870 }
871
872 // close the shade if it was open
Daniel Sandler11cf1782012-09-27 14:03:08 -0400873 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400874 visibilityChanged(false);
875
876 // If this click was on the intruder alert, hide that instead
877// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
878 }
879 }
880 /**
881 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
882 * This was added last-minute and is inconsistent with the way the rest of the notifications
883 * are handled, because the notification isn't really cancelled. The lights are just
884 * turned off. If any other notifications happen, the lights will turn back on. Steve says
885 * this is what he wants. (see bug 1131461)
886 */
887 protected void visibilityChanged(boolean visible) {
888 if (mPanelSlightlyVisible != visible) {
889 mPanelSlightlyVisible = visible;
890 try {
891 mBarService.onPanelRevealed();
892 } catch (RemoteException ex) {
893 // Won't fail unless the world has ended.
894 }
895 }
896 }
897
Chris Wren0c8275b2012-05-08 13:36:48 -0400898 /**
899 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
900 * about the failure.
901 *
902 * WARNING: this will call back into us. Don't hold any locks.
903 */
904 void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
905 removeNotification(key);
906 try {
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400907 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message);
Chris Wren0c8275b2012-05-08 13:36:48 -0400908 } catch (RemoteException ex) {
909 // The end is nigh.
910 }
911 }
912
913 protected StatusBarNotification removeNotificationViews(IBinder key) {
914 NotificationData.Entry entry = mNotificationData.remove(key);
915 if (entry == null) {
916 Slog.w(TAG, "removeNotification for unknown key: " + key);
917 return null;
918 }
919 // Remove the expanded view.
920 ViewGroup rowParent = (ViewGroup)entry.row.getParent();
921 if (rowParent != null) rowParent.removeView(entry.row);
Chris Wren8fd12652012-05-09 21:25:57 -0400922 updateExpansionStates();
Chris Wren0c8275b2012-05-08 13:36:48 -0400923 updateNotificationIcons();
924
925 return entry.notification;
926 }
927
928 protected StatusBarIconView addNotificationViews(IBinder key,
929 StatusBarNotification notification) {
930 if (DEBUG) {
931 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
932 }
933 // Construct the icon.
934 final StatusBarIconView iconView = new StatusBarIconView(mContext,
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400935 notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()),
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400936 notification.getNotification());
Chris Wren0c8275b2012-05-08 13:36:48 -0400937 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
938
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400939 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400940 notification.getUser(),
941 notification.getNotification().icon,
942 notification.getNotification().iconLevel,
943 notification.getNotification().number,
944 notification.getNotification().tickerText);
Chris Wren0c8275b2012-05-08 13:36:48 -0400945 if (!iconView.set(ic)) {
946 handleNotificationError(key, notification, "Couldn't create icon: " + ic);
947 return null;
948 }
949 // Construct the expanded view.
950 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
951 if (!inflateViews(entry, mPile)) {
952 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
953 + notification);
954 return null;
955 }
956
957 // Add the expanded view and icon.
958 int pos = mNotificationData.add(entry);
959 if (DEBUG) {
960 Slog.d(TAG, "addNotificationViews: added at " + pos);
961 }
Chris Wren8fd12652012-05-09 21:25:57 -0400962 updateExpansionStates();
Chris Wren0c8275b2012-05-08 13:36:48 -0400963 updateNotificationIcons();
964
965 return iconView;
966 }
967
Chris Wren8fd12652012-05-09 21:25:57 -0400968 protected boolean expandView(NotificationData.Entry entry, boolean expand) {
Chris Wren66757212012-05-14 10:05:42 -0400969 int rowHeight =
970 mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height);
971 ViewGroup.LayoutParams lp = entry.row.getLayoutParams();
972 if (entry.expandable() && expand) {
973 if (DEBUG) Slog.d(TAG, "setting expanded row height to WRAP_CONTENT");
974 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
Chris Wren8fd12652012-05-09 21:25:57 -0400975 } else {
Chris Wren66757212012-05-14 10:05:42 -0400976 if (DEBUG) Slog.d(TAG, "setting collapsed row height to " + rowHeight);
977 lp.height = rowHeight;
Chris Wren8fd12652012-05-09 21:25:57 -0400978 }
Chris Wren66757212012-05-14 10:05:42 -0400979 entry.row.setLayoutParams(lp);
980 return expand;
Chris Wren8fd12652012-05-09 21:25:57 -0400981 }
982
983 protected void updateExpansionStates() {
984 int N = mNotificationData.size();
985 for (int i = 0; i < N; i++) {
986 NotificationData.Entry entry = mNotificationData.get(i);
Chris Wren3ddab0d2012-08-02 16:52:21 -0400987 if (!entry.userLocked()) {
988 if (i == (N-1)) {
989 if (DEBUG) Slog.d(TAG, "expanding top notification at " + i);
990 expandView(entry, true);
Chris Wren8fd12652012-05-09 21:25:57 -0400991 } else {
Chris Wren3ddab0d2012-08-02 16:52:21 -0400992 if (!entry.userExpanded()) {
993 if (DEBUG) Slog.d(TAG, "collapsing notification at " + i);
994 expandView(entry, false);
995 } else {
996 if (DEBUG) Slog.d(TAG, "ignoring user-modified notification at " + i);
997 }
Chris Wren8fd12652012-05-09 21:25:57 -0400998 }
Chris Wren3ddab0d2012-08-02 16:52:21 -0400999 } else {
1000 if (DEBUG) Slog.d(TAG, "ignoring notification being held by user at " + i);
Chris Wren8fd12652012-05-09 21:25:57 -04001001 }
1002 }
1003 }
1004
Chris Wren0c8275b2012-05-08 13:36:48 -04001005 protected abstract void haltTicker();
1006 protected abstract void setAreThereNotifications();
1007 protected abstract void updateNotificationIcons();
1008 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
1009 protected abstract void updateExpandedViewPos(int expandedPosition);
Chris Wren8fd12652012-05-09 21:25:57 -04001010 protected abstract int getExpandedViewMaxHeight();
Jim Millerb4238e02012-05-14 15:26:20 -07001011 protected abstract boolean shouldDisableNavbarGestures();
Chris Wren0c8275b2012-05-08 13:36:48 -04001012
1013 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
Daniel Sandler26cda272012-05-22 15:44:08 -04001014 return parent != null && parent.indexOfChild(entry.row) == 0;
Chris Wren0c8275b2012-05-08 13:36:48 -04001015 }
1016
1017 public void updateNotification(IBinder key, StatusBarNotification notification) {
1018 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
1019
1020 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
1021 if (oldEntry == null) {
1022 Slog.w(TAG, "updateNotification for unknown key: " + key);
1023 return;
1024 }
1025
1026 final StatusBarNotification oldNotification = oldEntry.notification;
1027
1028 // XXX: modify when we do something more intelligent with the two content views
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001029 final RemoteViews oldContentView = oldNotification.getNotification().contentView;
1030 final RemoteViews contentView = notification.getNotification().contentView;
1031 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
1032 final RemoteViews bigContentView = notification.getNotification().bigContentView;
Chris Wren0c8275b2012-05-08 13:36:48 -04001033
1034 if (DEBUG) {
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001035 Slog.d(TAG, "old notification: when=" + oldNotification.getNotification().when
Chris Wren0c8275b2012-05-08 13:36:48 -04001036 + " ongoing=" + oldNotification.isOngoing()
1037 + " expanded=" + oldEntry.expanded
1038 + " contentView=" + oldContentView
Chris Wrene9e37722012-05-11 17:40:14 -04001039 + " bigContentView=" + oldBigContentView
Chris Wren0c8275b2012-05-08 13:36:48 -04001040 + " rowParent=" + oldEntry.row.getParent());
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001041 Slog.d(TAG, "new notification: when=" + notification.getNotification().when
Chris Wren0c8275b2012-05-08 13:36:48 -04001042 + " ongoing=" + oldNotification.isOngoing()
Chris Wrene9e37722012-05-11 17:40:14 -04001043 + " contentView=" + contentView
1044 + " bigContentView=" + bigContentView);
Chris Wren0c8275b2012-05-08 13:36:48 -04001045 }
1046
1047 // Can we just reapply the RemoteViews in place? If when didn't change, the order
1048 // didn't change.
Chris Wrene9e37722012-05-11 17:40:14 -04001049
1050 // 1U is never null
Chris Wren0c8275b2012-05-08 13:36:48 -04001051 boolean contentsUnchanged = oldEntry.expanded != null
Chris Wren0c8275b2012-05-08 13:36:48 -04001052 && contentView.getPackage() != null
1053 && oldContentView.getPackage() != null
1054 && oldContentView.getPackage().equals(contentView.getPackage())
1055 && oldContentView.getLayoutId() == contentView.getLayoutId();
Chris Wrene9e37722012-05-11 17:40:14 -04001056 // large view may be null
1057 boolean bigContentsUnchanged =
1058 (oldEntry.getLargeView() == null && bigContentView == null)
1059 || ((oldEntry.getLargeView() != null && bigContentView != null)
1060 && bigContentView.getPackage() != null
1061 && oldBigContentView.getPackage() != null
1062 && oldBigContentView.getPackage().equals(bigContentView.getPackage())
1063 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
Chris Wren0c8275b2012-05-08 13:36:48 -04001064 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001065 boolean orderUnchanged = notification.getNotification().when== oldNotification.getNotification().when
1066 && notification.getScore() == oldNotification.getScore();
Chris Wren0c8275b2012-05-08 13:36:48 -04001067 // score now encompasses/supersedes isOngoing()
1068
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001069 boolean updateTicker = notification.getNotification().tickerText != null
1070 && !TextUtils.equals(notification.getNotification().tickerText,
1071 oldEntry.notification.getNotification().tickerText);
Chris Wren0c8275b2012-05-08 13:36:48 -04001072 boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
Chris Wrene9e37722012-05-11 17:40:14 -04001073 if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
Chris Wren0c8275b2012-05-08 13:36:48 -04001074 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
1075 oldEntry.notification = notification;
1076 try {
1077 // Reapply the RemoteViews
Dianne Hackborna1940212012-06-28 16:07:22 -07001078 contentView.reapply(mContext, oldEntry.expanded, mOnClickHandler);
Chris Wrene9e37722012-05-11 17:40:14 -04001079 if (bigContentView != null && oldEntry.getLargeView() != null) {
Dianne Hackborna1940212012-06-28 16:07:22 -07001080 bigContentView.reapply(mContext, oldEntry.getLargeView(), mOnClickHandler);
Chris Wrene9e37722012-05-11 17:40:14 -04001081 }
Chris Wren0c8275b2012-05-08 13:36:48 -04001082 // update the contentIntent
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001083 final PendingIntent contentIntent = notification.getNotification().contentIntent;
Chris Wren0c8275b2012-05-08 13:36:48 -04001084 if (contentIntent != null) {
1085 final View.OnClickListener listener = makeClicker(contentIntent,
Daniel Sandler4f91efd2013-04-25 16:38:41 -04001086 notification.getPackageName(), notification.getTag(), notification.getId());
Chris Wren0c8275b2012-05-08 13:36:48 -04001087 oldEntry.content.setOnClickListener(listener);
1088 } else {
1089 oldEntry.content.setOnClickListener(null);
1090 }
1091 // Update the icon.
Daniel Sandler4f91efd2013-04-25 16:38:41 -04001092 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001093 notification.getUser(),
1094 notification.getNotification().icon, notification.getNotification().iconLevel,
1095 notification.getNotification().number,
1096 notification.getNotification().tickerText);
Chris Wren0c8275b2012-05-08 13:36:48 -04001097 if (!oldEntry.icon.set(ic)) {
1098 handleNotificationError(key, notification, "Couldn't update icon: " + ic);
1099 return;
1100 }
Chris Wren8fd12652012-05-09 21:25:57 -04001101 updateExpansionStates();
Chris Wren0c8275b2012-05-08 13:36:48 -04001102 }
1103 catch (RuntimeException e) {
1104 // It failed to add cleanly. Log, and remove the view from the panel.
1105 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
1106 removeNotificationViews(key);
1107 addNotificationViews(key, notification);
1108 }
1109 } else {
1110 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
Chris Wren8fd12652012-05-09 21:25:57 -04001111 if (DEBUG) Slog.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
1112 if (DEBUG) Slog.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
1113 if (DEBUG) Slog.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
Chris Wrenc9433ecb2012-06-27 17:37:03 -04001114 final boolean wasExpanded = oldEntry.userExpanded();
Chris Wren0c8275b2012-05-08 13:36:48 -04001115 removeNotificationViews(key);
1116 addNotificationViews(key, notification);
Chris Wrenc9433ecb2012-06-27 17:37:03 -04001117 if (wasExpanded) {
1118 final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
1119 expandView(newEntry, true);
Chris Wren5ae1ea82012-06-28 14:35:43 -04001120 newEntry.setUserExpanded(true);
Chris Wrenc9433ecb2012-06-27 17:37:03 -04001121 }
Chris Wren0c8275b2012-05-08 13:36:48 -04001122 }
1123
1124 // Update the veto button accordingly (and as a result, whether this row is
1125 // swipe-dismissable)
1126 updateNotificationVetoButton(oldEntry.row, notification);
1127
John Spurlock61e36832012-09-10 09:43:27 -04001128 // Is this for you?
1129 boolean isForCurrentUser = notificationIsForCurrentUser(notification);
1130 if (DEBUG) Slog.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
1131
Chris Wren0c8275b2012-05-08 13:36:48 -04001132 // Restart the ticker if it's still running
John Spurlock61e36832012-09-10 09:43:27 -04001133 if (updateTicker && isForCurrentUser) {
Chris Wren0c8275b2012-05-08 13:36:48 -04001134 haltTicker();
1135 tick(key, notification, false);
1136 }
1137
1138 // Recalculate the position of the sliding windows and the titles.
1139 setAreThereNotifications();
1140 updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1141
1142 // See if we need to update the intruder.
1143 if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) {
1144 if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification);
1145 // XXX: this is a hack for Alarms. The real implementation will need to *update*
1146 // the intruder.
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001147 if (notification.getNotification().fullScreenIntent == null) { // TODO(dsandler): consistent logic with add()
Chris Wren0c8275b2012-05-08 13:36:48 -04001148 if (DEBUG) Slog.d(TAG, "no longer intrudes!");
1149 mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
1150 }
1151 }
1152 }
John Spurlock36231282012-06-23 17:11:27 -04001153
1154 // Q: What kinds of notifications should show during setup?
1155 // A: Almost none! Only things coming from the system (package is "android") that also
1156 // have special "kind" tags marking them as relevant for setup (see below).
1157 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
Daniel Sandler4f91efd2013-04-25 16:38:41 -04001158 if ("android".equals(sbn.getPackageName())) {
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -04001159 if (sbn.getNotification().kind != null) {
1160 for (String aKind : sbn.getNotification().kind) {
John Spurlock36231282012-06-23 17:11:27 -04001161 // IME switcher, created by InputMethodManagerService
1162 if ("android.system.imeswitcher".equals(aKind)) return true;
1163 // OTA availability & errors, created by SystemUpdateService
1164 if ("android.system.update".equals(aKind)) return true;
1165 }
1166 }
1167 }
1168 return false;
1169 }
John Spurlock67ad3682012-06-26 17:42:00 -04001170
1171 public boolean inKeyguardRestrictedInputMode() {
1172 KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
1173 return km.inKeyguardRestrictedInputMode();
1174 }
Joe Onorato2314aab2010-04-08 16:41:23 -05001175}