Android中SystemUI解析

还未完成,待继续补充.

SystemUI概述

Android8.1.0 SystemUI代码统计如下:

1
2
3
4
5
6
7
8
9
10
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Java 892 23725 27078 143796
XML 1318 4337 20556 91781
Python 1 25 10 130
Bourne Shell 1 1 0 13
-------------------------------------------------------------------------------
SUM: 2212 28088 47644 235720
-------------------------------------------------------------------------------

从Android7.0开始,把Keyguard模块的代码也一起放到了SystemUI模块中,从代码统计来看,SystemUI中Java代码大概有14万行.
实际SystemUI与Framework有很多的交互,还有很多的代码是在Framework的其他模块里面.

4.4版本SystemUI中SystemBar启动流程如下:
"SystemUI启动流程分析"

主要类功能初步解析

ImmersiveModeConfirmation.java

首次隐藏状态栏和导航栏,进入沉浸模式是的提示

SystemUI包含的服务

SystemUI通过java启动的服务有13个,分别是:
1.TunerService 系统界面调谐器
系统界面调制器是安卓M加入的,当用户长按下拉菜单的齿轮卡3秒中就可以在setting app里加入界面调制器。
可以对状态栏、导航栏、勿扰模式以及锁屏界面进行局部定制.

2.KeyguardViewMediator
锁屏管理

3.Recents
最近应用模块,

4.VolumeUI
音量的调节

5.Divider

6.SystemBars
7.StorageNotification
8.PowerUI
9.RingtonePlayer
10.KeyboardUI
11.PipUI
12.ShortcutKeyDispatcher
13.VendorServices

通过xml启动的服务有6个,分别是:
1.SystemUIService
2.SystemUISecondaryUserService
3.TakeScreenshotService
4.ImageWallpaper
5.RecentsSystemUserService
6.DessertCaseDream
7.KeyguardService
8.DozeService

先以KeyguardViewMediator服务启动流程图为例进行分析,其它服务启动顺序与这个类似,只是功能不同而已。
"KeyguardViewMediator服务启动流程分析"

SystemBar的启动流程图如下:
"SystemBar服务启动流程分析"
PhoneStatusBar.java中的createAndAddWindows()会初始化并且加载SystemUI的绝大多数界面,包括statusbar与navigationbar,以及初始化QSHostView。

SystemUI的功能模块

SystemUI的功能模块:
a)Keyguard
锁屏和壁纸
b)StatusBar:电量、时间、SIM卡状态和信号、WIFI、Bluetooth、数据连接状态
通知、
c)NavigationBar
d)Shortcut
截图和录屏
e)Recents
f)PipUI
g)Tuner
h)RingtonePlayer

SystemUI重难点

  1. 滑动事件的处理

2.

状态栏

锁屏状态栏

状态栏反色

沉浸式状态栏

信号图标的刷新

凹口处理

导航栏 NavigationBar

导航栏中的Back键和HOME键的事件处理:
https://blog.csdn.net/span76/article/details/48441971
Recent键的处理呢?
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
中的onRecentsClick函数处理Recent键

导航栏的加载流程

基于Android8.1代码
对于NavigationBar的加载从PhoneWindowManager开始讲起,PhoneWindowManager主要用于处理几个实体按键或者虚拟按键(Home/Back/Recent/Power/Volume up/Volume down)等按键事件,包括音量加减,截图,长按Power调出关机界面,长按Home/Back/Recent键,短按Home/Back/Recent/Power键. 显示点击点的位置.BugReport
以及SystemUI的NavigationBar和StatusBar的加载.手势,方向,HDMI,
StatusBar,NavigationBar,Wallpaper,文字输入法和语言输入法等窗口的绘制
显示开机界面中的Android正在启动.

在PhoneWindowManager.java中与StatusBar和NavigationBar相关的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
public class PhoneWindowManager implements WindowManagerPolicy {
static final String TAG = "WindowManager";
...
boolean mSafeMode;
WindowState mStatusBar = null;
int mStatusBarHeight;
WindowState mNavigationBar = null;
boolean mHasNavigationBar = false;
boolean mNavigationBarCanMove = false; // can the navigation bar ever move to the side?
int mNavigationBarPosition = NAV_BAR_BOTTOM;
int[] mNavigationBarHeightForRotationDefault = new int[4];
int[] mNavigationBarWidthForRotationDefault = new int[4];
int[] mNavigationBarHeightForRotationInCarMode = new int[4];
int[] mNavigationBarWidthForRotationInCarMode = new int[4];
...
boolean mForceStatusBar;
boolean mForceStatusBarFromKeyguard;
private boolean mForceStatusBarTransparent;
int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
...
private final StatusBarController mStatusBarController = new StatusBarController();
private final BarController mNavigationBarController = new BarController("NavigationBar",
View.NAVIGATION_BAR_TRANSIENT,
View.NAVIGATION_BAR_UNHIDE,
View.NAVIGATION_BAR_TRANSLUCENT,
StatusBarManager.WINDOW_NAVIGATION_BAR,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
View.NAVIGATION_BAR_TRANSPARENT);
...
private ImmersiveModeConfirmation mImmersiveModeConfirmation;//沉浸模式
...
IStatusBarService getStatusBarService() {
synchronized (mServiceAquireLock) {
if (mStatusBarService == null) {
mStatusBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService("statusbar"));
}
return mStatusBarService;
}
}

StatusBarManagerInternal getStatusBarManagerInternal() {
synchronized (mServiceAquireLock) {
if (mStatusBarManagerInternal == null) {
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
}
return mStatusBarManagerInternal;
}
}
...
/** {@inheritDoc} */
@Override
public void init(Context context, IWindowManager windowManager,
WindowManagerFuncs windowManagerFuncs) {
mContext = context;
mWindowManager = windowManager;
mWindowManagerFuncs = windowManagerFuncs;
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
mAccessibilityShortcutController =
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
// Init display burn-in protection
boolean burnInProtectionEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_enableBurnInProtection);
// Allow a system property to override this. Used by developer settings.
boolean burnInProtectionDevMode =
SystemProperties.getBoolean("persist.debug.force_burn_in", false);
...
readConfigurationDependentBehaviors();
...
mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext);
...
}

/**
* Read values from config.xml that may be overridden depending on
* the configuration of the device.
* eg. Disable long press on home goes to recents on sw600dp.
*/
private void readConfigurationDependentBehaviors() {
final Resources res = mContext.getResources();
...
mNavBarOpacityMode = res.getInteger(
com.android.internal.R.integer.config_navBarOpacityMode);
}

@Override
public void setInitialDisplaySize(Display display, int width, int height, int density) {
// This method might be called before the policy has been fully initialized
// or for other displays we don't care about.
// TODO(multi-display): Define policy for secondary displays.
...
// Allow the navigation bar to move on non-square small devices (phones).
mNavigationBarCanMove = width != height && shortSizeDp < 600;

mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);

// Allow a system property to override this. Used by the emulator.
// See also hasNavigationBar().
String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
mHasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
mHasNavigationBar = true;
}
...
}
...
private boolean canHideNavigationBar() {
return mHasNavigationBar;
}
...
@Override
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
switch (attrs.type) {
case TYPE_SYSTEM_OVERLAY:
case TYPE_SECURE_SYSTEM_OVERLAY:
// These types of windows can't receive input events.
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
break;
case TYPE_STATUS_BAR:

// If the Keyguard is in a hidden state (occluded by another window), we force to
// remove the wallpaper and keyguard flag so that any change in-flight after setting
// the keyguard as occluded wouldn't set these flags again.
// See {@link #processKeyguardSetHiddenResultLw}.
if (mKeyguardOccluded) {
attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
break;
...
}

if (attrs.type != TYPE_STATUS_BAR) {
// The status bar is the only window allowed to exhibit keyguard behavior.
attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
...
}
....
@Override
public void onConfigurationChanged() {
// TODO(multi-display): Define policy for secondary displays.
Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
final Resources res = uiContext.getResources();

mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);

// Height of the navigation bar when presented horizontally at bottom
mNavigationBarHeightForRotationDefault[mPortraitRotation] =
mNavigationBarHeightForRotationDefault[mUpsideDownRotation] =
res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
mNavigationBarHeightForRotationDefault[mLandscapeRotation] =
mNavigationBarHeightForRotationDefault[mSeascapeRotation] = res.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_landscape);

// Width of the navigation bar when presented vertically along one side
mNavigationBarWidthForRotationDefault[mPortraitRotation] =
mNavigationBarWidthForRotationDefault[mUpsideDownRotation] =
mNavigationBarWidthForRotationDefault[mLandscapeRotation] =
mNavigationBarWidthForRotationDefault[mSeascapeRotation] =
res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);

if (ALTERNATE_CAR_MODE_NAV_SIZE) {
// Height of the navigation bar when presented horizontally at bottom
mNavigationBarHeightForRotationInCarMode[mPortraitRotation] =
mNavigationBarHeightForRotationInCarMode[mUpsideDownRotation] =
res.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_car_mode);
mNavigationBarHeightForRotationInCarMode[mLandscapeRotation] =
mNavigationBarHeightForRotationInCarMode[mSeascapeRotation] = res.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_landscape_car_mode);

// Width of the navigation bar when presented vertically along one side
mNavigationBarWidthForRotationInCarMode[mPortraitRotation] =
mNavigationBarWidthForRotationInCarMode[mUpsideDownRotation] =
mNavigationBarWidthForRotationInCarMode[mLandscapeRotation] =
mNavigationBarWidthForRotationInCarMode[mSeascapeRotation] =
res.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_width_car_mode);
}
}
...
private int getNavigationBarWidth(int rotation, int uiMode) {
if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
return mNavigationBarWidthForRotationInCarMode[rotation];
} else {
return mNavigationBarWidthForRotationDefault[rotation];
}
}
...
private int getNavigationBarHeight(int rotation, int uiMode) {
if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
return mNavigationBarHeightForRotationInCarMode[rotation];
} else {
return mNavigationBarHeightForRotationDefault[rotation];
}
}
...
/**
* Preflight adding a window to the system.
*
* Currently enforces that three window types are singletons:
* <ul>
* <li>STATUS_BAR_TYPE</li>
* <li>KEYGUARD_TYPE</li>
* </ul>
*
* @param win The window to be added
* @param attrs Information about the window to be added
*
* @return If ok, WindowManagerImpl.ADD_OKAY. If too many singletons,
* WindowManagerImpl.ADD_MULTIPLE_SINGLETON
*/
@Override
public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
switch (attrs.type) {
case TYPE_STATUS_BAR:
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.STATUS_BAR_SERVICE,
"PhoneWindowManager");
if (mStatusBar != null) {
if (mStatusBar.isAlive()) {
return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
}
}
mStatusBar = win;
mStatusBarController.setWindow(win);
setKeyguardOccludedLw(mKeyguardOccluded, true /* force */);
break;
case TYPE_NAVIGATION_BAR:
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.STATUS_BAR_SERVICE,
"PhoneWindowManager");
if (mNavigationBar != null) {
if (mNavigationBar.isAlive()) {
return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
}
}
mNavigationBar = win;
mNavigationBarController.setWindow(win);
mNavigationBarController.setOnBarVisibilityChangedListener(
mNavBarVisibilityListener, true);
if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
break;
case TYPE_NAVIGATION_BAR_PANEL:
case TYPE_STATUS_BAR_PANEL:
case TYPE_STATUS_BAR_SUB_PANEL:
case TYPE_VOICE_INTERACTION_STARTING:
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.STATUS_BAR_SERVICE,
"PhoneWindowManager");
break;
}
return ADD_OKAY;
}
...
/** {@inheritDoc} */
@Override
public void removeWindowLw(WindowState win) {
if (mStatusBar == win) {
mStatusBar = null;
mStatusBarController.setWindow(null);
} else if (mNavigationBar == win) {
mNavigationBar = null;
mNavigationBarController.setWindow(null);
}
}
...
/** {@inheritDoc} */
@Override
public int selectAnimationLw(WindowState win, int transit) {
if (PRINT_ANIM) Log.i(TAG, "selectAnimation in " + win
+ ": transit=" + transit);
if (win == mStatusBar) {
final boolean isKeyguard = (win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
final boolean expanded = win.getAttrs().height == MATCH_PARENT
&& win.getAttrs().width == MATCH_PARENT;
if (isKeyguard || expanded) {
return -1;
}
if (transit == TRANSIT_EXIT
|| transit == TRANSIT_HIDE) {
return R.anim.dock_top_exit;
} else if (transit == TRANSIT_ENTER
|| transit == TRANSIT_SHOW) {
return R.anim.dock_top_enter;
}
} else if (win == mNavigationBar) {
if (win.getAttrs().windowAnimations != 0) {
return 0;
...
return 0;
}
...
/** {@inheritDoc} */
@Override
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
final boolean keyguardOn = keyguardOn();
/*final*/ int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
final int metaState = event.getMetaState();
final int flags = event.getFlags();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final boolean longPress = (flags & KeyEvent.FLAG_LONG_PRESS) != 0;
...
}
...
private final Runnable mClearHideNavigationFlag = new Runnable() {
@Override
public void run() {
synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
// Clear flags.
mForceClearedSystemUiFlags &=
~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
}
mWindowManagerFuncs.reevaluateStatusBarVisibility();
}
};
...
/**
* Input handler used while nav bar is hidden. Captures any touch on the screen,
* to determine when the nav bar should be shown and prevent applications from
* receiving those touches.
* 当状态栏和导航栏处于隐藏状态的时候,点击屏幕,会显示出状态栏和导航栏,显示时间大概1s
*/
final class HideNavInputEventReceiver extends InputEventReceiver {
public HideNavInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}

@Override
public void onInputEvent(InputEvent event, int displayId) {
boolean handled = false;
try {
if (event instanceof MotionEvent
&& (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
final MotionEvent motionEvent = (MotionEvent)event;
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
// When the user taps down, we re-show the nav bar.
boolean changed = false;
synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
if (mInputConsumer == null) {
return;
}
// Any user activity always causes us to show the
// navigation controls, if they had been hidden.
// We also clear the low profile and only content
// flags so that tapping on the screen will atomically
// restore all currently hidden screen decorations.
int newVal = mResettingSystemUiFlags |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LOW_PROFILE |
View.SYSTEM_UI_FLAG_FULLSCREEN;
if (mResettingSystemUiFlags != newVal) {
mResettingSystemUiFlags = newVal;
changed = true;
}
// We don't allow the system's nav bar to be hidden
// again for 1 second, to prevent applications from
// spamming us and keeping it from being shown.
newVal = mForceClearedSystemUiFlags |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
if (mForceClearedSystemUiFlags != newVal) {
mForceClearedSystemUiFlags = newVal;
changed = true;
mHandler.postDelayed(mClearHideNavigationFlag, 1000);
}
}
if (changed) {
mWindowManagerFuncs.reevaluateStatusBarVisibility();
}
}
}
} finally {
finishInputEvent(event, handled);
}
}
}
...
@Override
public int adjustSystemUiVisibilityLw(int visibility) {
mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);

// Reset any bits in mForceClearingStatusBarVisibility that
// are now clear.
mResettingSystemUiFlags &= visibility;
// Clear any bits in the new visibility that are currently being
// force cleared, before reporting it.
return visibility & ~mResettingSystemUiFlags
& ~mForceClearedSystemUiFlags;
}
...
private boolean layoutStatusBar(Rect pf, Rect df, Rect of, Rect vf, Rect dcf, int sysui,
boolean isKeyguardShowing) {
// decide where the status bar goes ahead of time
if (mStatusBar != null) {
// apply any navigation bar insets
pf.left = df.left = of.left = mUnrestrictedScreenLeft;
pf.top = df.top = of.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight
+ mUnrestrictedScreenTop;
vf.left = mStableLeft;
vf.top = mStableTop;
vf.right = mStableRight;
vf.bottom = mStableBottom;

mStatusBarLayer = mStatusBar.getSurfaceLayer();

// Let the status bar determine its size.
mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */);

// For layout, the status bar is always at the top with our fixed height.
mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;

boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
boolean statusBarTranslucent = (sysui
& (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
if (!isKeyguardShowing) {
statusBarTranslucent &= areTranslucentBarsAllowed();
}

// If the status bar is hidden, we don't want to cause
// windows behind it to scroll.
if (mStatusBar.isVisibleLw() && !statusBarTransient) {
// Status bar may go away, so the screen area it occupies
// is available to apps but just covering them when the
// status bar is visible.
mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;

mContentTop = mVoiceContentTop = mCurTop = mDockTop;
mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
mContentRight = mVoiceContentRight = mCurRight = mDockRight;

if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " +
String.format(
"dock=[%d,%d][%d,%d] content=[%d,%d][%d,%d] cur=[%d,%d][%d,%d]",
mDockLeft, mDockTop, mDockRight, mDockBottom,
mContentLeft, mContentTop, mContentRight, mContentBottom,
mCurLeft, mCurTop, mCurRight, mCurBottom));
}
if (mStatusBar.isVisibleLw() && !mStatusBar.isAnimatingLw()
&& !statusBarTransient && !statusBarTranslucent
&& !mStatusBarController.wasRecentlyTranslucent()) {
// If the opaque status bar is currently requested to be visible,
// and not in the process of animating on or off, then
// we can tell the app that it is covered by it.
mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight;
}
if (mStatusBarController.checkHiddenLw()) {
return true;
}
}
return false;
}

private boolean layoutNavigationBar(int displayWidth, int displayHeight, int displayRotation,
int uiMode, int overscanLeft, int overscanRight, int overscanBottom, Rect dcf,
boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
boolean statusBarExpandedNotKeyguard) {
if (mNavigationBar != null) {
boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
// Force the navigation bar to its appropriate place and
// size. We need to do this directly, instead of relying on
// it to bubble up from the nav bar, because this needs to
// change atomically with screen rotations.
mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight,
displayRotation);
if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
// It's a system nav bar or a portrait screen; nav bar goes on bottom.
int top = displayHeight - overscanBottom
- getNavigationBarHeight(displayRotation, uiMode);
mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom);
mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
if (transientNavBarShowing) {
mNavigationBarController.setBarShowingLw(true);
} else if (navVisible) {
mNavigationBarController.setBarShowingLw(true);
mDockBottom = mTmpNavigationFrame.top;
mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
} else {
// We currently want to hide the navigation UI - unless we expanded the status
// bar.
mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
}
if (navVisible && !navTranslucent && !navAllowedHidden
&& !mNavigationBar.isAnimatingLw()
&& !mNavigationBarController.wasRecentlyTranslucent()) {
// If the opaque nav bar is currently requested to be visible,
// and not in the process of animating on or off, then
// we can tell the app that it is covered by it.
mSystemBottom = mTmpNavigationFrame.top;
}
} else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
// Landscape screen; nav bar goes to the right.
int left = displayWidth - overscanRight
- getNavigationBarWidth(displayRotation, uiMode);
mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight);
mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
if (transientNavBarShowing) {
mNavigationBarController.setBarShowingLw(true);
} else if (navVisible) {
mNavigationBarController.setBarShowingLw(true);
mDockRight = mTmpNavigationFrame.left;
mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
} else {
// We currently want to hide the navigation UI - unless we expanded the status
// bar.
mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
}
if (navVisible && !navTranslucent && !navAllowedHidden
&& !mNavigationBar.isAnimatingLw()
&& !mNavigationBarController.wasRecentlyTranslucent()) {
// If the nav bar is currently requested to be visible,
// and not in the process of animating on or off, then
// we can tell the app that it is covered by it.
mSystemRight = mTmpNavigationFrame.left;
}
} else if (mNavigationBarPosition == NAV_BAR_LEFT) {
// Seascape screen; nav bar goes to the left.
int right = overscanLeft + getNavigationBarWidth(displayRotation, uiMode);
mTmpNavigationFrame.set(overscanLeft, 0, right, displayHeight);
mStableLeft = mStableFullscreenLeft = mTmpNavigationFrame.right;
if (transientNavBarShowing) {
mNavigationBarController.setBarShowingLw(true);
} else if (navVisible) {
mNavigationBarController.setBarShowingLw(true);
mDockLeft = mTmpNavigationFrame.right;
// TODO: not so sure about those:
mRestrictedScreenLeft = mRestrictedOverscanScreenLeft = mDockLeft;
mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
} else {
// We currently want to hide the navigation UI - unless we expanded the status
// bar.
mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
}
if (navVisible && !navTranslucent && !navAllowedHidden
&& !mNavigationBar.isAnimatingLw()
&& !mNavigationBarController.wasRecentlyTranslucent()) {
// If the nav bar is currently requested to be visible,
// and not in the process of animating on or off, then
// we can tell the app that it is covered by it.
mSystemLeft = mTmpNavigationFrame.right;
}
}
// Make sure the content and current rectangles are updated to
// account for the restrictions from the navigation bar.
mContentTop = mVoiceContentTop = mCurTop = mDockTop;
mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
mContentRight = mVoiceContentRight = mCurRight = mDockRight;
mStatusBarLayer = mNavigationBar.getSurfaceLayer();
// And compute the final frame.
mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
mTmpNavigationFrame, mTmpNavigationFrame);
if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
if (mNavigationBarController.checkHiddenLw()) {
return true;
}
}
return false;
}

快捷开关

下拉通知栏

截图模块

防误触模块

亮度调节

PIP 画中画

双屏异显

Recent模块

在AndroidManifest.xml里面,我们注意到SystemUISecondaryUserService,对于每一个用户都有一个SystemUI进程,所以在用户切换的时候,需要用SystemUISecondaryUserService来确保新的用户进程创建.

1
2
3
4
5
6
<!-- Recents depends on every user having their own SystemUI process, so on user switch,
ensure that the process is created by starting this service.
-->
<service android:name="SystemUISecondaryUserService"
android:exported="true"
android:permission="com.android.systemui.permission.SELF" />

RecentsActivity.java的onCreate函数开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreDrawListener,
ColorExtractor.OnColorsChangedListener {
...

/** Called with the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFinishedOnStartup = false;

// In the case that the activity starts up before the Recents component has initialized
// (usually when debugging/pushing the SysUI apk), just finish this activity.
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp == null) {
mFinishedOnStartup = true;
finish();
return;
}

SystemUI 新功能

隐藏导航栏

悬浮按钮

单手模式(小屏模式)

单手键盘

口袋模式(防误触模式)

凹口设计

灭屏动画

翻转静音

1.来电静音
2.计时器和闹钟静音

双击唤醒

指关节截屏

字母手势

面部解锁

画中画

异屏双显

沉浸模式

##SB和NB颜色

通知栏和状态栏功能

通知

通知:允许通知.通知方式:状态栏上显示、在屏幕顶部悬浮显示、在锁屏上显示.

通知栏:

通知栏下拉规则:1)智能判断,有通知时,下拉显示通知,无通知时,下拉显示开关
2)依据下拉位置来判断:左边下拉显示通知页,右边下拉显示开关页
在通知栏显示流量信息.

状态栏:

显示运营商名称
有通知时显示图标
显示实时网速
显示电量百分比,电量百分比显示方式

Demo模式命令调试SystemUI

在SystemUI目录有一个Readme.md文件,在8.1.0版本中内容如下:

1
* [Demo Mode](/packages/SystemUI/docs/demo_mode.md)

我们到此目录查看demo_mode.md文件
如下:
Demo Mode for the Android System UI
Demo mode for the status bar allows you to force the status bar into a fixed state, useful for taking screenshots with a consistent status bar state, or testing different status icon permutations. Demo mode is available in recent versions of Android.

Enabling demo mode

Demo mode is protected behind a system setting. To enable it for a device, run:

1
adb shell settings put global sysui_demo_allowed 1

Protocol

The protocol is based on broadcast intents, and thus can be driven via the command line (adb shell am broadcast) or an app (Context.sendBroadcast).

Broadcast action

1
com.android.systemui.demo

Commands

Commands and subcommands (below) are sent as string extras in the broadcast
intent.


Commands are sent as string extras with key command (required). Possible values are:

Command Subcommand Argument Description
enter Enters demo mode, bar state allowed to be modified (for convenience, any of the other non-exit commands will automatically flip demo mode on, no need to call this explicitly in practice)
exit Exits demo mode, bars back to their system-driven state
battery Control the battery display
level Sets the battery level (0 - 100)
plugged Sets charging state (true, false)
powersave Sets power save mode (true, anything else)
network Control the RSSI display
airplane show to show icon, any other value to hide
fully Sets MCS state to fully connected (true, false)
wifi show to show icon, any other value to hide
level Sets wifi level (null or 0-4)
mobile show to show icon, any other value to hide
datatype Values: 1x, 3g, 4g, e, g, h, lte, roam, any other value to hide
level Sets mobile signal strength level (null or 0-4)
carriernetworkchange Sets mobile signal icon to carrier network change UX when disconnected (show to show icon, any other value to hide)
sims Sets the number of sims (1-8)
nosim show to show icon, any other value to hide
bars Control the visual style of the bars (opaque, translucent, etc)
mode Sets the bars visual style (opaque, translucent, semi-transparent)
status Control the system status icons
volume Sets the icon in the volume slot (silent, vibrate, any other value to hide)
bluetooth Sets the icon in the bluetooth slot (connected, disconnected, any other value to hide)
location Sets the icon in the location slot (show, any other value to hide)
alarm Sets the icon in the alarm_clock slot (show, any other value to hide)
sync Sets the icon in the sync_active slot (show, any other value to hide)
tty Sets the icon in the tty slot (show, any other value to hide)
eri Sets the icon in the cdma_eri slot (show, any other value to hide)
mute Sets the icon in the mute slot (show, any other value to hide)
speakerphone Sets the icon in the speakerphone slot (show, any other value to hide)
notifications Control the notification icons
visible false to hide the notification icons, any other value to show
clock Control the clock display
millis Sets the time in millis
hhmm Sets the time in hh:mm

Examples

Enter demo mode

1
adb shell am broadcast -a com.android.systemui.demo -e command enter

Exit demo mode

1
adb shell am broadcast -a com.android.systemui.demo -e command exit

Set the clock to 12:31

1
2
adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm
1231

Set the wifi level to max

1
2
adb shell am broadcast -a com.android.systemui.demo -e command network -e wifi
show -e level 4

Show the silent volume icon

1
2
adb shell am broadcast -a com.android.systemui.demo -e command status -e volume
silent

Empty battery, and not charging (red exclamation point)

1
2
adb shell am broadcast -a com.android.systemui.demo -e command battery -e level
0 -e plugged false

Hide the notification icons

1
2
adb shell am broadcast -a com.android.systemui.demo -e command notifications -e
visible false

Exit demo mode

1
adb shell am broadcast -a com.android.systemui.demo -e command exit

Example demo controller app in AOSP

1
frameworks/base/tests/SystemUIDemoModeController

Example script (for screenshotting purposes)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/sh
CMD=$1

if [[ $ADB == "" ]]; then
ADB=adb
fi

if [[ $CMD != "on" && $CMD != "off" ]]; then
echo "Usage: $0 [on|off] [hhmm]" >&2
exit
fi

if [[ "$2" != "" ]]; then
HHMM="$2"
fi

$ADB root || exit
$ADB wait-for-devices
$ADB shell settings put global sysui_demo_allowed 1

if [ $CMD == "on" ]; then
$ADB shell am broadcast -a com.android.systemui.demo -e command enter || exit
if [[ "$HHMM" != "" ]]; then
$ADB shell am broadcast -a com.android.systemui.demo -e command clock -e
hhmm ${HHMM}
fi
$ADB shell am broadcast -a com.android.systemui.demo -e command battery -e
plugged false
$ADB shell am broadcast -a com.android.systemui.demo -e command battery -e
level 100
$ADB shell am broadcast -a com.android.systemui.demo -e command network -e
wifi show -e level 4
$ADB shell am broadcast -a com.android.systemui.demo -e command network -e
mobile show -e datatype none -e level 4
$ADB shell am broadcast -a com.android.systemui.demo -e command notifications
-e visible false
elif [ $CMD == "off" ]; then
$ADB shell am broadcast -a com.android.systemui.demo -e command exit
fi

导航栏和状态栏的显示与隐藏

POLICY_CONTROL实现
隐藏虚拟键及顶部状态栏:
adb shell settings put global policy_control immersive.full=
隐藏顶部状态栏(底部虚拟键会显示):
adb shell settings put global policy_control immersive.status=

隐藏虚拟键(顶部状态栏会显示):
adb shell settings put global policy_control immersive.navigation=*
恢复原来的设置:
adb shell settings put global policy_control null

参考文献

Doze模式
Android5.1 系统之省电模式探索一启动流程
《深入理解Android 卷III》第七章 深入理解SystemUI

NavigationBar相关
Android SystemUI中HOME key的处理
去除首次进入沉浸模式气泡提示
Android 修改横屏角度为顺时针270度

SystemUI 调谐器 TunerService的打开方式

Android官方架构组件Navigation:大巧不工的Fragment管理框架