SystemUI总结
https://www.jianshu.com/p/1a1c53cc44c3
android 下拉状态栏(SystemUI)常见修改记录
https://blog.csdn.net/Mis_wenwen/article/details/73468788
systemui整体介绍
https://cloud.tencent.com/developer/article/1155393
StatusBar 系统图标
https://cloud.tencent.com/developer/article/1186839
Notification流程
https://www.cnblogs.com/cczheng-666/p/10953193.html
SystemUI9.0系统应用图标加载流程
https://blog.csdn.net/tj_shenzhendaxue/article/details/88698042
Android 8.0 SystemUI(四):二说顶部 StatusBar 信号等图标
https://cloud.tencent.com/developer/article/1350097
导航栏
https://blog.csdn.net/qq_31530015/article/details/53507968
SystemUI分类
状态栏StatusBar:通知消息提示和状态展现
导航栏NavigationBar:返回,HOME,Recent
锁屏界面KeyGuard:锁屏模块可以看做单独的应用,提供基本的手机个人隐私保护
最近任务Recents:近期应用管理,以堆叠栈的形式展现。
通知栏Notification Panel:QuickSettings和Notification展示系统或应用通知内容。提供快速系统设置开关
音量调节对话框VolumeUI:来用展示或控制音量的变化:媒体音量、铃声音量与闹钟音量
截屏界面ScreenShot::长按电源键+音量下键后截屏,用以展示截取的屏幕照片/内容
电源管理PowerUI:主要处理和Power相关的事件,比如省电模式切换、电池电量变化和开关屏事件等
RingtonePlayer:铃声播放
StackDivider:控制管理分屏
PipUI:提供对于画中画模式的管理
9.0源码目录:
SystemUI启动流程
由init进程->Zygote进程->SystemServer进程->systemui启动
按电源键,系统上电,从固定地址加载固化在ROM的bootloader代码到RAM中执行,bootloader引导程序将os拉起,系统拉起后进行初始化和解析启动init进程。
startOtherServices()中,通过调用AMS的systemReady()方法通知AMS准备就绪。systemReady()拥有一个名为goingCallback的Runnable实例作为参数,当AMS完成systemReady()处理后会回调Runnable.run()
ps:frameworks/base/service/java/com/android/server/SystemServer.java
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
......
if (mSystemReady) {
// If we're done calling all the receivers, run the next "boot phase" passed in
// by the SystemServer
if (goingCallback != null) {
goingCallback.run();
}
return;
}
......
}
AMS在回调中启动SystemUI
mActivityManagerService.systemReady(() -> {
......
startSystemUi(context, windowManagerF);
......
}
static final void startSystemUi(Context context, WindowManagerService windowManager) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
windowManager.onSystemUiStarted();
}
在开启服务后,WMS回调SystemUI已开启
ps:frameworks/base/service/core/java/com/android/server/wm/WindowManagerService.java
public void onSystemUiStarted() {
mPolicy.onSystemUiStarted();
}
ps:frameworks/base/core/java/android/view/WindowManagerPolicy.java
void onSystemUiStarted();
SystemUIService继承了Service
public class SystemUIService extends Service {
......
@Override
public void onCreate() {
super.onCreate();
((SystemUIApplication) getApplication()).startServicesIfNeeded();
......
}
......
}
ps:frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
public void startServicesIfNeeded() {
String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
android.util.Log.e("sunyang", "SystemUIApplication startServicesIfNeeded names :" + names);
startServicesIfNeeded(names);
}
private void startServicesIfNeeded(String[] services) {
......
try {
cls = Class.forName(clsName);
mServices[i] = (SystemUI) cls.newInstance();
}
......
mServices[i].mContext = this;
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
android.util.Log.e("sunyang", "SystemUIApplication startServicesIfNeeded running :" + mServices[i]);
mServices[i].start();
......
}
loading: com.android.systemui.Dependency
loading: com.android.systemui.util.NotificationChannels
loading: com.android.systemui.statusbar.CommandQueue$CommandQueueStart
loading: com.android.systemui.keyguard.KeyguardViewMediator
loading: com.android.systemui.recents.Recents
loading: com.android.systemui.volume.VolumeUI
loading: com.android.systemui.stackdivider.Divider
loading: com.android.systemui.SystemBars
loading: com.android.systemui.usb.StorageNotification
loading: com.android.systemui.power.PowerUI
loading: com.android.systemui.media.RingtonePlayer
loading: com.android.systemui.keyboard.KeyboardUI
loading: com.android.systemui.pip.PipUI
loading: com.android.systemui.shortcut.ShortcutKeyDispatcher
loading: com.android.systemui.VendorServices
loading: com.android.systemui.util.leak.GarbageMonitor$Service
loading: com.android.systemui.LatencyTester
loading: com.android.systemui.globalactions.GlobalActionsComponent
loading: com.android.systemui.ScreenDecorations
loading: com.android.systemui.fingerprint.FingerprintDialogImpl
loading: com.android.systemui.SliceBroadcastRelayHandler
loading: com.android.systemui.screenswitch.ScreenSwitchUI
loading: com.android.systemui.presssensor.PressSensorUI
统一接口
ps:frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUI.java
public abstract class SystemUI implements SysUiServiceProvider {
...
public abstract void start();
处理系统状态变化的回调,这里的状态变化包括:时区变更,字体大小变更,输入模式变更,屏幕大小变更,屏幕方向变更等
protected void onConfigurationChanged(Configuration newConfig) {}
用来将模块的内部状态dump到输出流中,这个方法主要是辅助调试所用。开发者可以在开发过程中,通过adb shell执行dump来了解系统的内部状态
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
系统启动完成的回调方法
protected void onBootCompleted() {}
...
}
状态栏:StatusBar Notification QuickSettings
ps:frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java
@Override
public void start() {
if (DEBUG) Log.d(TAG, "start");
createStatusBarFromConfig(); 创建了状态栏
}
ps:frameworks/base/packages/SystemUI/res/value/config.xml
config_statusBarComponent com.android.systemui.statusbar.phone.StatusBar
private void createStatusBarFromConfig() {
if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
final String clsName = mContext.getString(R.string.config_statusBarComponent);
if (clsName == null || clsName.length() == 0) {
throw andLog("No status bar component configured", null);
}
Class> cls = null;
try { 类加载器加载对应类
cls = mContext.getClassLoader().loadClass(clsName);
} catch (Throwable t) {
throw andLog("Error loading status bar component: " + clsName, t);
}
try { 反射创建对象
mStatusBar = (SystemUI) cls.newInstance();
} catch (Throwable t) {
throw andLog("Error creating status bar component: " + clsName, t);
}
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mComponents;
mStatusBar.start();
if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
}
调用StatusBar的start() 状态栏和导航栏的启动过程
1、获取IStatusBarService,IStatusBarService是运行于system_server的系统服务,它接受操作状态栏/导航栏的请求并将其转发给StatusBar,为了保证SystemUI意外退出不会发生信息丢失,IStatusBarService保存了所有状态栏和导航栏进行显示或处理的信息副本
2、将一个继承自IStatusBar.Stub的CommandQueue的实例注册到IStatusBarService以建立通信,并将信息副本取回
3、创建状态栏与导航栏的窗口createAndAddWindows()创建statusbar
4、使用从IStatusBarService取回的信息副本
7.1.3 理解IStatusBarService
public class StatusBarManagerService extends IStatusBarService.Stub {}
与WMS、IMS等系统服务一样,StatusBarManagerService 在ServerThread中创建
if (!isWatch) {
traceBeginAndSlog("StartStatusBarManagerService");
try {
statusBar = new StatusBarManagerService(context, wm);
ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
} catch (Throwable e) {
reportWtf("starting StatusBarManagerService", e);
}
traceEnd();
}
构造方法:
public StatusBarManagerService(Context context, WindowManagerService windowManager) {
mContext = context;
mWindowManager = windowManager;
LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
}
获取保存在其中的副本
StatusBarManagerService.registerStatusBar
ps:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
@Override
public void start() {
...
......
//mCommandQueue是CommandQueue类的一个实例,CommandQueue继承自IStatusBar.Stub,因此它是IStatusBar
//的Bn端。在完成注册后,这个Binder对象的Bp端将会保存在IStatusBarService中。
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
mCommandQueue.addCallbacks(this);
//switches存储了一些杂项:禁用功能列表,SystemUIVisiblity,是否在导航栏中显示虚拟的菜单键,输入法窗口
//是否可见,输入法窗口是否消费BACK键,是否接入了实体键盘,实体键盘是否被启用
int[] switches = new int[9];
ArrayList binders = new ArrayList<>();
ArrayList iconSlots = new ArrayList<>();
ArrayList icons = new ArrayList<>();
Rect fullscreenStackBounds = new Rect();
Rect dockedStackBounds = new Rect();
try {
//2 向IStatusBarService进行注册,并获取保存在IStatusBarService中的信息副本
mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
fullscreenStackBounds, dockedStackBounds);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
//3 创建状态栏与导航栏的窗口
createAndAddWindows(); 创建statusbar
......
mLockscreenUserManager.setUpWithPresenter(this, mEntryManager);
//禁用某些功能
mCommandQueue.disable(switches[0], switches[6], false /* animate */);
//设置SystemUiVisibility
setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
fullscreenStackBounds, dockedStackBounds);
//设置菜单键的可见性
topAppWindowChanged(switches[2] != 0);
// StatusBarManagerService has a back up of IME token and it's restored here.
//根据输入法窗口的可见性调整导航栏的样式
setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);
//依次向系统状态栏区添加状态图标
// Set up the initial icon state
int N = iconSlots.size();
for (int i=0; i < N; i++) {
mCommandQueue.setIcon(iconSlots.get(i), icons.get(i));
}
//导航栏和状态栏启动完成
...
}
}
解析步骤2
mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
fullscreenStackBounds, dockedStackBounds);
@Override
public void registerStatusBar(IStatusBar bar, List iconSlots,
List iconList, int switches[], List binders,
Rect fullscreenStackBounds, Rect dockedStackBounds) {
//权限检查,安全为避免其他应用调用,因此调用者必须具有系统级权限android.Manifest.permission.STATUS_BAR_SERVICE
enforceStatusBarService();
//1、将bar参数保存在mBar成员中,mBar类型IStatusBar,就是CommandQueue的Bp端,后面StatusBarManagerService通过mBar与StatusBar 通信
Slog.i(TAG, "registerStatusBar bar=" + bar);
mBar = bar;
try {
mBar.asBinder().linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
mBar = null;
notifyBarAttachChanged();
}
}, 0);
} catch (RemoteException e) {
}
notifyBarAttachChanged();
//2、接下来依次为调用者返回信息副本,系统状态区的图标列表
synchronized (mIcons) {
for (String slot : mIcons.keySet()) {
iconSlots.add(slot);
iconList.add(mIcons.get(slot));
}
}
//switches中的杂项
synchronized (mLock) {
switches[0] = gatherDisableActionsLocked(mCurrentUserId, 1);
switches[1] = mSystemUiVisibility;
switches[2] = mMenuVisible ? 1 : 0;
switches[3] = mImeWindowVis;
switches[4] = mImeBackDisposition;
switches[5] = mShowImeSwitcher ? 1 : 0;
switches[6] = gatherDisableActionsLocked(mCurrentUserId, 2);
switches[7] = mFullscreenStackSysUiVisibility;
switches[8] = mDockedStackSysUiVisibility;
binders.add(mImeToken);
fullscreenStackBounds.set(mFullscreenStackBounds);
dockedStackBounds.set(mDockedStackBounds);
}
}
StatusBarManagerService的工作方式
当他接受到操作状态栏和导航栏的请求时,首先将请求信息保存到副本中,然后将mBar发送给StatusBar
1、StatusBarManagerService是状态栏和导航栏在system_server中的代理。所有对状态栏和导航栏的需求都通过此实例操作
2、StatusBarManagerService保存了状态栏和导航栏所需的信息副本,用于在SystemUI意外退出后恢复
设置系统状态区图标
@Override
public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
String contentDescription) {
enforceStatusBar();
synchronized (mIcons) {
StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.SYSTEM, iconId,
iconLevel, 0, contentDescription);
//Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
//1、将图标信息保存在副本中
mIcons.put(slot, icon);
//2、将设置请求发送给StatusBar
if (mBar != null) {
try {
mBar.setIcon(slot, icon);
} catch (RemoteException ex) {
}
}
}
}
7.1.4 SystemUI的体系结构
1、SystemUIService,一个普通的android服务,它以一个容器的角色运行于SystemUI进程中。在其内部运行着多个服务,包括StatusBar
2、IStatusBarService,即系统服务StatusBarManagerService是状态栏和导航栏向外界提供服务的接口,运行在system_server进程中
3、IStatusBar,即SystemUI中的CommandQueue是联系StatusBarManagerService与StatusBar的桥梁
4、SystemUI还包括ImageWallpaper等功能实现。通过startService、startActivity
StatusBar.java
super_status_bar.xml
@+id/status_bar_container
status_bar.xml
@+id/notification_lights_out
@+id/status_bar_contents
@+id/clock
@+id/netspeed
status_bar_expanded.xml
qs_frame
brightness_mirror.xml
顶部状态栏
status_bar.xml
@+id/notification_lights_out
@+id/status_bar_contents
heads_up_status_bar_layout
@+id/status_bar_left_side
@+id/clock
@+id/notification_icon_area
@+id/system_icon_area
@+id/netspeed
system_icons
@+id/notification_lights_out
一个ImageView,一般不可见。在SystemUIVisiblity中有一个名为SYSTEM_UI_FLAG_LOW_PROFILE的标记。当应用程序希望用户注意力集中在它所显示的内容上,可以在其SystemUIVisiblity中添加这一标记。SYSTEM_UI_FLAG_LOW_PROFILE会使状态栏和导航栏进入低辨识度模式。低辨识度模式下状态栏不会显示任何信息,只是在黑色背景中显示一个灰色圆点。这个黑色圆点为notification_lights_out
@+id/status_bar_contents
一个LinearLayout,状态栏上各种信息的显示场所
@+id/status_bar_contents
heads_up_status_bar_layout
@+id/status_bar_left_side
@+id/clock
@+id/notification_icon_area
@+id/system_icon_area
@+id/netspeed
system_icons
@+id/notification_icon_area
通知信息
@+id/system_icon_area
7.2 深入理解状态栏
作为一个将所有信息集中显示的场所,状态栏对需要显示的信息分为以下5种
1、通知信息:它可以在状态栏左侧显示一个图标以引起用户的注意,并在下拉栏中为用户显示更加详细的信息。这是状态栏所能提供的信息显示服务之中最灵活的一种功能。它对信息种类以及来源没有做任何限制。使用者可以通过StatusBarManagerService所提供的接口向状态栏中添加或移除一条通知信息
2、时间信息:显示在状态栏最右侧的一个小型数字时钟,是一个名为Clock的继承自TextView的控件。它监听了几个和时间相关的广播:ACTION_TIME_TICK、ACTION_TIME_CHANGED、ACTION_TIMEZONE_CHANGED、ACTION_CONFIGURATION_CHANGED。当其中一个广播到来时从Calendar类中获取当前的系统时间,然后进行字符串格式化后显示出来。时间信息的维护工作在状态栏内部完成,因此外界无法通过api修改时间信息和显示行为
3、电量信息:显示在数字时钟左侧的电池图标,用于提示当前的电量情况。它是一个被BatteryController类所管理的ImageView。BatteryController通过监听android.action.BATTERY_CHANGED广播从BetteryService中获取电量信息,并根据电量信息 选择一个合适的电池图标显示在ImageView上。同时间信息一样,这也是在状态栏内部维护的,外界无法干预状态栏对电量信息的显示行为
4、信号信息:显示在电量信息的左侧的一系列ImageView,用于显示系统当前的WIFI、移动网络的信息的范畴。他们被NetworkController类维护,NetworkController监听一系列与信号相关的广播如WIFI_STATE_CHANGED_ACTION、ACTION_SIM_STATE_CHANGED、ACTION_AIRPLANE_MODE_CHANGED等,并在这些广播到来时显示、更改或移除相关的ImageView
5、系统状态图标区:这个区域用一系列图标标识系统当前的状态,位于信号左侧。StatusBarManagerService通过setIcon() 为外界提供修改系统状态图标区的途径
7.2.1 状态栏窗口的创建与控件数结构
1 状态栏窗口的创建
public void createAndAddWindows() {
addStatusBarWindow();
}
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationData.Entry entry,
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.row.notifyHeightChanged(true /* needsAnimation */);
updateFooter();
}
public void lockScrollTo(NotificationData.Entry entry) {
mStackScroller.lockScrollTo(entry.row);
}
public void requestDisallowLongPressAndDismiss() {
mStackScroller.requestDisallowLongPress();
mStackScroller.requestDisallowDismiss();
}
});
mRemoteInputManager.getController().addCallback(mStatusBarWindowManager);
4 通过 mStatusBarWindowManager.add创建状态栏的窗口
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
2 为状态栏创建WindowManager.LayoutParams
public void add(View statusBarView, int barHeight) {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
mLp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,//状态栏的宽度为充满整个屏幕宽度
barHeight,//getStatusBarHeight()
WindowManager.LayoutParams.TYPE_STATUS_BAR,//窗口类型,可以显示在应用窗口之上
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE//状态栏不接受按键事件
//这一标记将使得状态栏接受导致设备唤醒的触摸事件,通常这一事件会在interceptMotionBeforeQueueing()
//的过程中被用于唤醒设备(或从变暗状态下恢复),而InputDispatcher会阻止这一事件发送给窗口
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
//允许状态栏支持触摸事件序列的拆分
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT);
//状态栏的Surface像素格式为支持透明度
mLp.token = new Binder();
mLp.gravity = Gravity.TOP;
mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("StatusBar");
mLp.packageName = mContext.getPackageName();
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mStatusBarView = statusBarView;
mBarHeight = barHeight;
mWindowManager.addView(mStatusBarView, mLp);
mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
}
当用户按下状态栏导致窗帘下拉时,StatusBarWindowManager通过mWindowManager.updateViewLayout(mStatusBarView, mLp)修改窗口的LayoutParams高度为MATCH_PARENT,即全屏显示,移除WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,可以监听back键收起窗帘
状态栏的高度,资源定义在frameworks\base\core\res\res\values\dimens.xml
1 通过getStatusBarHeight()方法获取状态栏的高度
public int getStatusBarHeight() {
if (mNaturalBarHeight < 0) {
final Resources res = mContext.getResources();
mNaturalBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
}
return mNaturalBarHeight;
}
3 创建状态栏的控件树
protected void makeStatusBarView() {
...
inflateStatusBarWindow(context);
...
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
mStatusBarView = (PhoneStatusBarView) fragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanel);
mStatusBarView.setScrimController(mScrimController);
mStatusBarView.setBouncerShowing(mBouncerShowing);
if (mHeadsUpAppearanceController != null) {
// This view is being recreated, let's destroy the old one
mHeadsUpAppearanceController.destroy();
}
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow);
setAreThereNotifications();
checkBarModes();
}).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
}
加载了状态栏根布局 super_status_bar.xml
protected void inflateStatusBarWindow(Context context) {
mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);
}
状态栏根布局 StatusBarWindowView,截获ACTION_DOWN的触摸事件后,会修改窗口的高度为MATCH_PARENT,然后将其他跟随触摸,实现卷帘的下拉效果。
顶部状态栏 status_bar_container FrameLayout
包括:notification statusIcon signal 电池 时钟的添加和更新
statusIcon的初始化与更新
系统状态的显示,比如蓝牙、闹铃、定位、省流量开关
frameworks/base/core/res/res/values/config.xml
ps:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
Fragement加载的布局 status_bar.xml
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.status_bar, container, false);
}
在CollapsedStatusBarFragment.java onViewCreated中加载statusIcons
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStatusBar = (PhoneStatusBarView) view;
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
mStatusBar.go(savedInstanceState.getInt(EXTRA_PANEL_STATE));
}
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
mDarkIconManager.setShouldLog(true);
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
mClockView = mStatusBar.findViewById(R.id.clock);
showSystemIconArea(false);
showClock(false);
initEmergencyCryptkeeperText();
initOperatorName();
}
顶部状态栏Fragement加载的布局 status_bar.xml
布局包括:
status_bar_container FrameLayout 顶部状态栏
status_bar_expanded.xml QuickSettings NotificationExpand
brightness_mirror.xml亮度调节
顶部状态栏:notification statusIcon signal 电池 时钟的添加和更新
顶部状态栏创建fragment、CollapsedStatusBarFragment
其中加载status_bar.xml 自定义组合控件
LinearLayout 横向布局包括
status_bar_cotents->
clock
notification_icon_area 显示的都是notifications,如三方和系统的一些通知
system_icons.xml->
statusIcons显示的一些系统状态,如蓝牙、闹铃
signal_cluster_view.xml显示的是信号相关的view,如wifi,cell信号格 battery 独立图标,电池电量显示、时间显示
clock
notification_icon_area 显示的都是notifications,如三方和系统的一些通知
system_icons.xml->
statusIcons显示的一些系统状态,如蓝牙、闹铃
signal_cluster_view.xml显示的是信号相关的view,如wifi,cell信号格
battery 独立图标,电池电量显示、时间显示
注:
8.0 中顶部状态栏 notification icon 部分逻辑有了很大的改动。对比 7.0,多出了个NotificationInflater。并且,icon 和 expand 的逻辑流程是几乎相同的。
增加了分析 notification icon 相关流程的复杂度
StatusBarWindowView.java(包括 1、顶部状态栏 2、亮度调节 3、QuickSettings Notification Expand)
负责整体框架绘制,点击事件监听等
一说:顶部statusBar初始化、分块、statusIcon块的添加和更新
StatusIcon块,主要负责的是系统状态的显示,比如蓝牙、闹铃、定位、省流量开关等。
这些Icon,都是系统预定好了是哪些。并在一个配置文件定义了slot,或者说是标签。
如果你想加一个新类型图标,首先要修改的是这个文件中的config_statusBarIcons数组
ps:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
在onViewCreated中,加载了statusIcons布局
87 @Override
88 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
89 super.onViewCreated(view, savedInstanceState);
90 mStatusBar = (PhoneStatusBarView) view;
91 if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
92 mStatusBar.go(savedInstanceState.getInt(EXTRA_PANEL_STATE));
93 }
一说:顶部statusBar初始化、分块、statusIcon块的添加和更新
94 mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
95 mDarkIconManager.setShouldLog(true);
96 Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
二说:内容计划是信号群icon与电池、时钟相关
97 mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
98 mClockView = mStatusBar.findViewById(R.id.clock);
99 showSystemIconArea(false);
100 showClock(false);
101 initEmergencyCryptkeeperText();
102 initOperatorName();
103 }
ps:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
DarkIconManager为内部类构造方法,StatusBarIconController状态栏图标的控制类
public static class DarkIconManager extends IconManager {
。。。
97 public DarkIconManager(LinearLayout linearLayout) {
98 super(linearLayout);
99 mIconHPadding = mContext.getResources().getDimensionPixelSize(
100 R.dimen.status_bar_icon_padding);
101 mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
102 }
。。。
}
PhoneStatusBarView.java成为基类,顶部状态栏的view
二说:内容计划是信号群icon与电池、时钟相关
三说:notification icon相关
public interface StatusBarIconController {
...
public void addIconGroup(IconManager iconManager);
}
config_statusBarIcons res/values/Strings.xml
实现类
public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
构造方法初始化所有图标
public StatusBarIconControllerImpl(Context context) {
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
Dependency.get(ConfigurationController.class).addCallback(this);
...
}
}
StatusBarIconList.java 父类的构造方法
public class StatusBarIconList {
private ArrayList mSlots = new ArrayList<>();
mSlots 包含所有图标信息
public StatusBarIconList(String[] slots) {
final int N = slots.length;
for (int i=0; i < N; i++) {
mSlots.add(new Slot(slots[i], null));
}
}
}
@Override
public void addIconGroup(IconManager group) {
mIconGroups.add(group);
List allSlots = getSlots();
for (int i = 0; i < allSlots.size(); i++) {
Slot slot = allSlots.get(i);
List holders = slot.getHolderListInViewOrder();
boolean blocked = mIconBlacklist.contains(slot.getName());
for (StatusBarIconHolder holder : holders) {
int tag = holder.getTag();
int viewIndex = getViewIndex(getSlotIndex(slot.getName()), holder.getTag());
group.onIconAdded(viewIndex, slot.getName(), blocked, holder);
}
}
}
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
StatusBarIconController内部类
public static class IconManager implements DemoMode {
public IconManager(ViewGroup group) {
... 图标的尺寸
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
...
}
}
添加图标 根据类型
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
switch (holder.getType()) {
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
case TYPE_WIFI:
return addSignalIcon(index, slot, holder.getWifiState());
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
}
return null;
}
Dependency.java
Dependency.start()中初始化很多控制类
public class Dependency extends SystemUI {
@Override
public void start() {
// TODO: Think about ways to push these creation rules out of Dependency to cut down
// on imports.
mProviders.put(TIME_TICK_HANDLER, () -> {
HandlerThread thread = new HandlerThread("TimeTick");
thread.start();
return new Handler(thread.getLooper());
});
mProviders.put(BG_LOOPER, () -> {
HandlerThread thread = new HandlerThread("SysUiBg",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
return thread.getLooper();
});
mProviders.put(MAIN_HANDLER, () -> new Handler(Looper.getMainLooper()));
mProviders.put(ActivityStarter.class, () -> new ActivityStarterDelegate());
mProviders.put(ActivityStarterDelegate.class, () ->
getDependency(ActivityStarter.class));
mProviders.put(AsyncSensorManager.class, () ->
new AsyncSensorManager(mContext.getSystemService(SensorManager.class)));
mProviders.put(BluetoothController.class, () ->
new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER)));
mProviders.put(LocationController.class, () ->
new LocationControllerImpl(mContext, getDependency(BG_LOOPER)));
...
}
StatusBar.java start()
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
在此完成所有StatusIcon的初始化和状态监听,完成图标的隐藏或添加
VPN提示、Ethernet图标、Wifi图标、Airplane提示、NoSim提示,移动网络图标等共6类
导航栏
BACK/HOME/RECENT
7.3.1 导航栏的创建
//1 是否需要导航栏
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
if (showNav) {
createNavigationBar();
}
protected void createNavigationBar() {
mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
mNavigationBar = (NavigationBarFragment) fragment;
if (mLightBarController != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
});
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.navigation_bar, container, false);
}
是否使用导航栏位于 config_showNavigationBar 的取值,有物理按键的设备
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavigationBarView = (NavigationBarView) view;
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
mNavigationBarView.setComponents(mRecents, mDivider, mStatusBar.getPanel());
mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
if (savedInstanceState != null) {
mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
}
//1 prepareNavigationBarView()负责为NavigationBarView中的虚拟按键设置用于响应用户触摸事件的监听器
prepareNavigationBarView();
checkNavBarModes();
setDisabled2Flags(mDisabledFlags2);
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
notifyNavigationBarScreenOn();
mOverviewProxyService.addCallback(mOverviewProxyListener);
}
NavigationBarView根控件定义了2套导航栏的控件树,水平方向@id/rot0,垂直方向@id/rot90
public static View create(Context context, FragmentListener listener) {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle("NavigationBar");
lp.accessibilityTitle = context.getString(R.string.nav_bar);
lp.windowAnimations = 0;
View navigationBarView = LayoutInflater.from(context).inflate(
R.layout.navigation_bar_window, null);
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
if (navigationBarView == null) return null;
context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
NavigationBarFragment fragment = new NavigationBarFragment();
fragmentHost.getFragmentManager().beginTransaction()
.replace(R.id.navigation_bar_frame, fragment, TAG)
.commit();
fragmentHost.addTagListener(TAG, listener);
return navigationBarView;
}
横导航栏
navigation_bar_height
竖导航栏
navigation_bar_height_landscape
调整导航栏尺寸,全交给PhoneWindowManager
frameworks/base/core/res/res/values/dimens.xml
防误触的死区
res/values/dimens.xml
navigation_bar_deadzone_size
7.3.2 虚拟按键的工作原理
1 从触摸事件到按键事件的映射
KeyButtonView将触摸事件转换为按键事件
public boolean onTouchEvent(MotionEvent ev) {
final boolean showSwipeUI = mOverviewProxyService.shouldShowSwipeUpUI();
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
mGestureAborted = false;
}
if (mGestureAborted) {
setPressed(false);
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownTime = SystemClock.uptimeMillis();
mLongClicked = false;
setPressed(true);
// Use raw X and Y to detect gestures in case a parent changes the x and y values
mTouchDownX = (int) ev.getRawX();
mTouchDownY = (int) ev.getRawY();
if (mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
} else {
// Provide the same haptic feedback that the system offers for virtual keys.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
if (!showSwipeUI) {
playSoundEffect(SoundEffectConstants.CLICK);
}
removeCallbacks(mCheckLongPress);
postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
x = (int)ev.getRawX();
y = (int)ev.getRawY();
boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > (mIsVertical
? NavigationBarCompat.getQuickScrubTouchSlopPx()
: NavigationBarCompat.getQuickStepTouchSlopPx());
boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > (mIsVertical
? NavigationBarCompat.getQuickStepTouchSlopPx()
: NavigationBarCompat.getQuickScrubTouchSlopPx());
if (exceededTouchSlopX || exceededTouchSlopY) {
// When quick step is enabled, prevent animating the ripple triggered by
// setPressed and decide to run it on touch up
setPressed(false);
removeCallbacks(mCheckLongPress);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
if (mCode != 0) {
sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
}
removeCallbacks(mCheckLongPress);
break;
case MotionEvent.ACTION_UP:
final boolean doIt = isPressed() && !mLongClicked;
setPressed(false);
final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150;
if (showSwipeUI) {
if (doIt) {
// Apply haptic feedback on touch up since there is none on touch down
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
playSoundEffect(SoundEffectConstants.CLICK);
}
} else if (doHapticFeedback && !mLongClicked) {
// Always send a release ourselves because it doesn't seem to be sent elsewhere
// and it feels weird to sometimes get a release haptic and other times not.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
}
if (mCode != 0) {
if (doIt) {
sendEvent(KeyEvent.ACTION_UP, 0);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
} else {
sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
}
} else {
// no key code, just a regular ImageView
if (doIt && mOnClickListener != null) {
mOnClickListener.onClick(this);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
}
removeCallbacks(mCheckLongPress);
break;
}
return true;
}
2 按键事件的发送
void sendEvent(int action, int flags, long when) {
mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT)
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(mCode)
.addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action)
.addTaggedData(MetricsEvent.FIELD_FLAGS, flags));
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
InputManager.getInstance().injectInputEvent(ev,
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
7.3.4 关于导航栏的其他话题
1 菜单键的可见性
PhoneWindow.generateLayout()
2 修改BACK的图标
StatusBar.java
setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);
3 导航栏方向的选择
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (DEBUG) Log.d(TAG, String.format(
"onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
//检查NavigationBarView处于竖直状态或水平状态
final boolean newVertical = w > 0 && h > w;
if (newVertical != mVertical) {
mVertical = newVertical;
//Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
//当状态变化时,会在@id/rot0 @id/rot90 之间选择
reorient();
notifyVerticalChangedListener(newVertical);
}
postCheckForInvalidLayout("sizeChanged");
super.onSizeChanged(w, h, oldw, oldh);
}
public void reorient() {
updateCurrentView();
((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
mDeadZone.onConfigurationChanged(mCurrentRotation);
// force the low profile & disabled states into compliance
mBarTransitions.init();
setMenuVisibility(mShowMenu, true /* force */);
if (DEBUG) {
Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
}
// Resolve layout direction if not resolved since components changing layout direction such
// as changing languages will recreate this view and the direction will be resolved later
if (!isLayoutDirectionResolved()) {
resolveLayoutDirection();
}
updateTaskSwitchHelper();
updateNavButtonIcons();
getHomeButton().setVertical(mVertical);
}
private void updateCurrentView() {
//1 获取屏幕旋转角度0 90 180 270
final int rot = mDisplay.getRotation();
for (int i=0; i<4; i++) {
mRotatedViews[i].setVisibility(View.GONE);
}
//选择对应方向为当前view,设置为可见
mCurrentView = mRotatedViews[rot];
mCurrentView.setVisibility(View.VISIBLE);
mNavigationInflaterView.setAlternativeOrder(rot == Surface.ROTATION_90);
mNavigationInflaterView.updateButtonDispatchersCurrentView();
updateLayoutTransitionsEnabled();
mCurrentRotation = rot;
}
mRotatedViews数组初始化位于onFinishInflate()
@Override
public void onFinishInflate() {
mNavigationInflaterView = findViewById(R.id.navigation_inflater);
mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
DockedStackExistsListener.register(mDockedListener);
updateRotatedViews();
}
private void updateRotatedViews() {
竖直
mRotatedViews[Surface.ROTATION_0] =
mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
水平
mRotatedViews[Surface.ROTATION_270] =
mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
updateCurrentView();
}
导航栏选择不同控件树时机在onSizeChanged(),然后reorient()通过mDisplay.getRotation()获取屏幕方向,在mRotatedViews数组中选择2种控件
7.3.5 导航栏总结
1 核心工作是将用户的触摸事件转换成相应的按键事件,并发送给InputDispatcher,主要实现者是导航栏内的KeyButtonView
2 PhoneWindowManager对导航栏进行布局
7.4 禁用状态栏和导航栏的功能
锁屏下,隐藏虚拟按键,通过密码或图案进行解锁保护,不希望用户拉出下来窗口
7.4.1 如何禁用状态栏和导航栏的功能
StatusBarManager.java
public void disable(int what) {
try {
final IStatusBarService svc = getService();
if (svc != null) {
svc.disable(what, mToken, mContext.getPackageName());
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
禁止下拉
public static final int DISABLE_EXPAND = View.STATUS_BAR_DISABLE_EXPAND;
隐藏状态栏中的通知图标
public static final int DISABLE_NOTIFICATION_ICONS = View.STATUS_BAR_DISABLE_NOTIFICATION_ICONS;
禁止通知图标
public static final int DISABLE_NOTIFICATION_ALERTS
= View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS;
禁止Ticker
public static final int DISABLE_NOTIFICATION_TICKER
= View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
隐藏状态栏中系统状态图标区、信号区、电量、时钟
public static final int DISABLE_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO;
public static final int DISABLE_HOME = View.STATUS_BAR_DISABLE_HOME;
public static final int DISABLE_RECENT = View.STATUS_BAR_DISABLE_RECENT;
public static final int DISABLE_BACK = View.STATUS_BAR_DISABLE_BACK;
public static final int DISABLE_CLOCK = View.STATUS_BAR_DISABLE_CLOCK;
public static final int DISABLE_SEARCH = View.STATUS_BAR_DISABLE_SEARCH;
@Deprecated
public static final int DISABLE_NAVIGATION =
View.STATUS_BAR_DISABLE_HOME | View.STATUS_BAR_DISABLE_RECENT;
public static final int DISABLE_NONE = 0x00000000;
使用这个功能需要系统权限 android.permission.STATUS_BAR
7.4.2 StatusBarManagerService对禁用标记的维护
@Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);
}
@Override
public void disableForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
synchronized (mLock) {
disableLocked(userId, what, token, pkg, 1);
}
}
7.4.3 状态栏与导航栏对禁用标记的响应
7.5 理解SystemUIVisibility
第三方用户使用,提供给他们接口,用于隐藏 显示 导航栏 状态栏
通知栏Notification Panel
QuickSettings和Notification展示系统或应用通知内容。提供快速系统设置开关
Notification流程
初始化通知栏区域
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
mStatusBarView = (PhoneStatusBarView) fragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanel);传递 NotificationPanelView 显示下拉UI控制
...
}
获取到 notification_icon_area,FrameLayout转为ViewGroup,调用 notificationIconAreaController 获取通知要显示的view(LinearLayout),
如果已经有显示的view,通过 view 父布局将其自身remove,然后再重新addView。最后将 mNotificationIconAreaInner 显示出来(设置透明度为1,visibility为VISIBLE)
public void initNotificationIconArea(NotificationIconAreaController
notificationIconAreaController) {
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
mNotificationIconAreaInner =
notificationIconAreaController.getNotificationInnerAreaView();
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
notificationIconArea.addView(mNotificationIconAreaInner);
// Default to showing until we know otherwise.
showNotificationIconArea(false);
}
动画隐藏通知栏
当状态栏下拉时,状态栏中的图标icon会慢慢的变成透明和不可见,就是通过hideSystemIconArea(true), hideNotificationIconArea(true)
public void hideNotificationIconArea(boolean animate) {
animateHide(mNotificationIconAreaInner, animate);
}
public void showNotificationIconArea(boolean animate) {
animateShow(mNotificationIconAreaInner, animate);
}
getNotificationInnerAreaView()方法中看看通知栏icon对应的容器
public View getNotificationInnerAreaView() {
return mNotificationIconArea;
}
protected void initializeNotificationAreaViews(Context context) {
reloadDimens(context);
LayoutInflater layoutInflater = LayoutInflater.from(context);
mNotificationIconArea = inflateIconArea(layoutInflater);
mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
R.id.notificationIcons);
mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
}
protected View inflateIconArea(LayoutInflater inflater) {
return inflater.inflate(R.layout.notification_icon_area, null);
}
notification_icon_area.xml
notification_icon_area(FrameLayout) 中添加 notification_icon_area_inner(LinearLayout),
每一个通知对应的bean为 NotificationData,创建 Notification 添加到 NotificationIconContainer(FrameLayout)中
statusBar 的start()中注册 NotificationListenerWithPlugins 作为系统service监听通知消息
start()->
创建Notification的监听
mNotificationListener = Dependency.get(NotificationListener.class);
......
mNotificationListener.setUpWithPresenter(this, mEntryManager);
public class NotificationListener extends NotificationListenerWithPlugins {
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationEntryManager entryManager) {
mPresenter = presenter;
mEntryManager = entryManager;
try {
registerAsSystemService(mContext,
new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
UserHandle.USER_ALL);
} catch (RemoteException e) {
Log.e(TAG, "Unable to register notification listener", e);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
mPresenter.getHandler().post(() -> {
mEntryManager.removeNotification(key, rankingMap);
});
}
}
ps:NotificationEntryManager.java
@Override
public void addNotification(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) throws InflationException {
...
NotificationData.Entry shadeEntry = createNotificationViews(notification);
}
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
throws InflationException {
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
NotificationData.Entry entry = new NotificationData.Entry(sbn);
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
// Construct the expanded view.
inflateViews(entry, mListContainer.getViewParentForNotification(entry));
return entry;
}
private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
final StatusBarNotification sbn = entry.notification;
if (entry.row != null) {
entry.reset();
updateNotification(entry, pmUser, sbn, entry.row);
} else {
new RowInflaterTask().inflate(mContext, parent, entry,
row -> {
bindRow(entry, pmUser, sbn, row);
updateNotification(entry, pmUser, sbn, row);
});
}
}
ps:RowInflaterTask.java
public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
RowInflationFinishedListener listener) {
if (TRACE_ORIGIN) {
mInflateOrigin = new Throwable("inflate requested here");
}
mListener = listener;
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
mEntry = entry;
entry.setInflationTask(this);
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
ps:NotificationEntryManager.java
@Override
public void updateNotification(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) {
try {
updateNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
屏蔽通知栏
ps:frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
mHandler.post(new EnqueueNotificationRunnable(userId, r));
QuickSettings下拉栏
初始化QuickSettings
// Set up the quick settings tile panel
View container = mStatusBarWindow.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
Dependency.get(ExtensionController.class)
.newExtension(QS.class)
.withPlugin(QS.class)
.withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new)
.withDefault(QSFragment::new)
.build());
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
(visible) -> {
mBrightnessMirrorVisible = visible;
updateScrimController();
});
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
((QSFragment) qs).setHost(qsh);
mQSPanel = ((QSFragment) qs).getQsPanel();
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mKeyguardStatusBar.setQSPanel(mQSPanel);
}
});
}
QSFragment中增加qs布局
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme));
return inflater.inflate(R.layout.qs_panel, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mQSPanel = view.findViewById(R.id.quick_settings_panel);
mQSDetail = view.findViewById(R.id.qs_detail);
mHeader = view.findViewById(R.id.header);
mFooter = view.findViewById(R.id.qs_footer);
mContainer = view.findViewById(id.quick_settings_container);
mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
mQSAnimator = new QSAnimator(this,
mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
mQSCustomizer = view.findViewById(R.id.qs_customize);
mQSCustomizer.setQs(this);
if (savedInstanceState != null) {
setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
int[] loc = new int[2];
View edit = view.findViewById(android.R.id.edit);
edit.getLocationInWindow(loc);
int x = loc[0] + edit.getWidth() / 2;
int y = loc[1] + edit.getHeight() / 2;
mQSCustomizer.setEditLocation(x, y);
mQSCustomizer.restoreInstanceState(savedInstanceState);
}
SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
}
在Fragment中添加view,QSPanle
1、QS排序
res/values/config.xml
ame="quick_settings_tiles_default" translatable="false">
wifi,cell,battery,dnd,flashlight,rotation,bt,airplane
2、QS中的行列
public class QSPanel extends LinearLayout implements Tunable, Callback, BrightnessMirrorListener {}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
final TunerService tunerService = Dependency.get(TunerService.class);
tunerService.addTunable(this, QS_SHOW_BRIGHTNESS);
if (mHost != null) {
setTiles(mHost.getTiles());
}
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.addCallback(this);
}
}
public void setTiles(Collection tiles) {
setTiles(tiles, false);
}
public void setTiles(Collection tiles, boolean collapsedView) {
if (!collapsedView) {
mQsTileRevealController.updateRevealedTiles(tiles);
}
for (TileRecord record : mRecords) {
mTileLayout.removeTile(record);
record.tile.removeCallback(record.callback);
}
mRecords.clear();
for (QSTile tile : tiles) {
addTile(tile, collapsedView);
}
}
protected TileRecord addTile(final QSTile tile, boolean collapsedView) {
final TileRecord r = new TileRecord();
r.tile = tile;
r.tileView = createTileView(tile, collapsedView);
final QSTile.Callback callback = new QSTile.Callback() {
...
}
}
ps:QSTileHost.java
public QSTileView createTileView(QSTile tile, boolean collapsedView) {
for (int i = 0; i < mQsFactories.size(); i++) {
QSTileView view = mQsFactories.get(i).createTileView(tile, collapsedView);
if (view != null) {
return view;
}
}
throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
}
QSTileView.java
下拉图标的自定义控件布局
QSPanel是下拉栏的根 布局,在其中加载PagedTileLayout(ViewPager)
public class PagedTileLayout extends ViewPager implements QSTileLayout {
@Override
public boolean updateResources() {
// Update bottom padding, useful for removing extra space once the panel page indicator is
// hidden.
setPadding(0, 0, 0,
getContext().getResources().getDimensionPixelSize(
R.dimen.qs_paged_tile_layout_padding_bottom));
boolean changed = false;
for (int i = 0; i < mPages.size(); i++) {
changed |= mPages.get(i).updateResources();
}
if (changed) {
distributeTiles();
}
return changed;
}
设置adapter
private void distributeTiles() {
if (DEBUG) Log.d(TAG, "Distributing tiles");
final int NP = mPages.size();
for (int i = 0; i < NP; i++) {
mPages.get(i).removeAllViews();
}
int index = 0;
final int NT = mTiles.size();
for (int i = 0; i < NT; i++) {
TileRecord tile = mTiles.get(i);
if (mPages.get(index).isFull()) {
if (++index == mPages.size()) {
if (DEBUG) Log.d(TAG, "Adding page for "
+ tile.tile.getClass().getSimpleName());
mPages.add((TilePage) LayoutInflater.from(getContext())
.inflate(R.layout.qs_paged_page, this, false));
}
}
if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
+ index);
mPages.get(index).addTile(tile);
}
if (mNumPages != index + 1) {
mNumPages = index + 1;
while (mPages.size() > mNumPages) {
mPages.remove(mPages.size() - 1);
}
if (DEBUG) Log.d(TAG, "Size: " + mNumPages);
mPageIndicator.setNumPages(mNumPages);
setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
setCurrentItem(0, false);
}
}
内部类TileLayout 中定义行
public static class TilePage extends TileLayout {
private int mMaxRows = 3; 行
public TilePage(Context context, AttributeSet attrs) {
super(context, attrs);
updateResources();
}
@Override
public boolean updateResources() {
final int rows = getRows();
boolean changed = rows != mMaxRows;
if (changed) {
mMaxRows = rows;
requestLayout();
}
return super.updateResources() || changed;
}
TileLayout.java
public boolean updateResources() {
final Resources res = mContext.getResources();
final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); 列
mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side);
if (mColumns != columns) {
mColumns = columns;
requestLayout();
return true;
}
return false;
}
3、QS中各个Tile实现
public abstract class QSTileImpl implements QSTile {}
LocationTile
1、首先tile需要继承QSTileImpl
public class LocationTile extends QSTileImpl {}
2、添加interface 作为Controller类
public interface LocationController extends CallbackController {
boolean isLocationActive();
boolean isLocationEnabled();
boolean setLocationEnabled(boolean enabled);
public interface LocationChangeCallback {
default void onLocationActiveChanged(boolean active) {}
default void onLocationSettingsChanged(boolean locationEnabled) {}
}
}
3、接口的实现类ControllerImpl,这里一般都是用来接收广播信息来处理的流程
public class LocationControllerImpl extends BroadcastReceiver implements LocationController {}
4、在自定义的tile类中首先要在构造函数中加入Controller,然后还需要重写如下这些方法,其中handleUpdateState()方法是核心方法,这里去设置QS的state的各个状态等
public LocationTile(QSHost host) {
super(host);
mController = Dependency.get(LocationController.class);
mKeyguard = Dependency.get(KeyguardMonitor.class);
}
QSTileImpl.java
public abstract TState newTileState();
abstract protected void handleClick();
abstract protected void handleUpdateState(TState state, Object arg);
具体逻辑都在LocationTile.java
增加新的状态栏图标和控制逻辑
增加QS 手电筒和控制逻辑
信号塔刷新流程
public class MobileSignalController extends SignalController<
MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> {
内部类监听信号变化
class MobilePhoneStateListener extends PhoneStateListener {
public MobilePhoneStateListener(int subId, Looper looper) {
super(subId, looper);
}
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
if (DEBUG) {
Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
}
mSignalStrength = signalStrength;
updateTelephony();
}
}