admin管理员组

文章数量:1574959

SystemUI 启动流程

SystemUI通常包含状态栏,下拉栏。

启动流程分析
SystemUI 包含的功能很多,有状态栏,导航栏,近期任务,PowerUI,音量调节进度条,截图,壁纸等等都在SystemUI里面.其中有些功能是按需启动,比如:近期任务和截图.但是想状态栏和导致栏就是一直运行的,他们是通过SystemUIService来启动运行的.

在负责启动系统各个服务的SystemServer.java中,它的大致流程是SystemServer.java -> main() - > run()-> startOtherServices().至于为什么是从main方法开始,因为java 语法是这样的…

在startOtherServices() 中通过调用ActivityManagerService.systemReady()方法通知AMS系统已经就绪。这个systemReady()拥有一个名为goingCallback的Runnable实例作为参数。顾名思义,当AMS完成对systemReady()的处理后将会回调这一Runnable的run()方法.

mActivityManagerService.systemReady(new Runnable() {
@Override
public void run() {
Slog.i(TAG, "Making services ready");
......
try {
startSystemUi(context);
} catch (Throwable e) {
reportWtf("starting System UI", e);
}
....
}
}
Sys

temServer.java启动其它服务时startOtherServices会调用 startSystemUi来启动SystemUIService服务.

static final void startSystemUi(Context context) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.OWNER);
}
context.startServiceAsUser会调用ContextImpl.java中startServiceAsUser,

startServiceAsUser直接转调startServiceCommon

private ComponentName startServiceCommon(Intent service, UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess();
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException( "Not allowed to start service " + service+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException( "Unable to start service " + service + ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
return null;
}
}
startServiceCommon通过ActivityManager.startService 到ActivityManagerService.startService,完成SystemUIService服务的启动。

 启动SystemUIService服务之后,SystemUIService.onCreate会被调用

public void onCreate() {
super.onCreate();
((SystemUIApplication) getApplication()).startServicesIfNeeded();
}
接着SystemUIApplication.startServicesIfNeeded判断是否需要启动,先判断sys.boot_completed属性值,后面会依次调用SystemUI 的start() 方法.

public void startServicesIfNeeded() {
if (mServicesStarted) {
return;
}

if (!mBootCompleted) {
// check to see if maybe it was already completed long before we began
// see ActivityManagerService.finishBooting()
if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
mBootCompleted = true;
if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
}
}

Log.v(TAG, "Starting SystemUI services.");
final int N = SERVICES.length;
for (int i=0; i<N; i++) {
Class<?> cl = SERVICES[i];
if (DEBUG) Log.d(TAG, "loading: " + cl);
try {
mServices[i] = (SystemUI)cl.newInstance();
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
}
mServices[i].mContext = this;
//这里设置上下文对象,最后会在PhoneStatusBar里面makeStatusBarView
//方法用来用来加载super_status_bar.xml
 
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
 
if (mBootCompleted) {
mServices[i].onBootCompleted();
}
}
mServicesStarted = true;
 
}

sys.boot_completed属性值,在系统的boot启动完成时,ActivityManagerService中会进行设置.其中SERVICES数组中的全部服务都是继承 的SystemUI这个抽象类.

SystemUI启动的主要的服务
下面看一下所有的子服务,他们并不是Android 里面经常提到真正的服务,只是继承了SystemUI这个抽象类的服务

 
private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recent.Recents.class,
com.android.systemui.volume.VolumeUI.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class
};

子服务有KeyguardViewMediator,Recents,VolumeUI,SystemBars,StorageNotification,PowerUI,RingtonePlayer,

KeyguardViewMediator为锁屏模块, 包含锁屏机制;

Recents 为近期任务列表;

VolumeUI为全局音量控制UI;

SystemBars为系统栏;

StorageNotification 为存储信息通知栏;

PowerUI 为电源界面;

RingtonePlayer 为铃声播放;

其中的SystemBars 就是启动状态栏的一个核心服务.具体流程如下:

SystemUIApplication.startServicesIfNeeded()会将调用SystemBars.start()

SystemBars.java

@Override
public void start() {
if (DEBUG) Log.d(TAG, "start");
//ServiceMonitor是个监听器,这里就是监听这个BAR_SERVICE_COMPONENT是否改变
mServiceMonitor = new ServiceMonitor(TAG, DEBUG,
mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this);
//这里看到注释,如果Service不存在 ,就调用onNoService->createStatusBarFromConfig();
mServiceMonitor.start(); // will call onNoService if no remote service is found
}
mServiceMonitor.start(); 将会通过handler 调用startService() 方法. 由于SystemBars实现了ServiceMonitor.Callbacks 接口, startService()就可以回调onNoService()方法,接着转掉createStatusBarFromConfig(),其中config_statusBarComponent 字符串就是com.android.systemui.statusbar.phone.PhoneStatusBar,所以最终会调用PhoneStatusBar.start(). PhoneStatusBar是SystemUI的核心类.

我们先来分析ServiceMonitor是如何处理再分析PhoneStatusBar.start() 的启动.

onNoService->createStatusBarFromConfig()也就是启动我们的config_statusBarComponent : PhoneStatusBar


public ServiceMonitor(String ownerTag, boolean debug,
Context context, String settingKey, Callbacks callbacks) {
mTag = ownerTag + ".ServiceMonitor";
mDebug = debug;
mContext = context;
mSettingKey = settingKey;
mCallbacks = callbacks;
}
 
public void start() {
// listen for setting changes
ContentResolver cr = mContext.getContentResolver();
cr.registerContentObserver(Settings.Secure.getUriFor(mSettingKey),
false /*notifyForDescendents*/, mSettingObserver, UserHandle.USER_ALL);
 
// listen for package/component changes
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mContext.registerReceiver(mBroadcastReceiver, filter);
 
mHandler.sendEmptyMessage(MSG_START_SERVICE);
}
这个类就是来监听这个Settings.Secure.BAR_SERVICE_COMPONENT的改变,并看情况如何启动Service。再看mHandler的处理
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_START_SERVICE:
startService();
break;
case MSG_CONTINUE_START_SERVICE:
continueStartService();
break;
case MSG_STOP_SERVICE:
stopService();
break;
case MSG_PACKAGE_INTENT:
packageIntent((Intent)msg.obj);
break;
case MSG_CHECK_BOUND:
checkBound();
break;
case MSG_SERVICE_DISCONNECTED:
serviceDisconnected((ComponentName)msg.obj);
break;
}
}
}; 
private void startService() {
//获取Service的ComponentName
mServiceName = getComponentNameFromSetting();
if (mDebug) Log.d(mTag, "startService mServiceName=" + mServiceName);
if (mServiceName == null) {
mBound = false;
//如果没有Service,就回调,从之前的看,就是start PhoneStatusBar
mCallbacks.onNoService();
} else {
//销毁已经存在的status bar
long delay = mCallbacks.onServiceStartAttempt();
    //喜闻乐见的bindService,说破天就是在启动PhoneStatusBar
mHandler.sendEmptyMessageDelayed(MSG_CONTINUE_START_SERVICE, delay);
}
}
 
private ComponentName getComponentNameFromSetting() {
String cn = Settings.Secure.getStringForUser(mContext.getContentResolver(),
mSettingKey, UserHandle.USER_CURRENT);
return cn == null ? null : ComponentName.unflattenFromString(cn);
}
 
****SystemBars.java**** 

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 = (BaseStatusBar) 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());
 
}
继续分析PhoneStatusBar.start(),会发现主要是调用了startKeyguard()和addNavigationBar()和super.start().

其中startKeyguard() 是解锁页面相关的,

addNavigationBar()是添加虚拟导航菜单,

super.start()是调用父类BaseStatusBar的start() 方法.

BaseStatusBar的start()会调用createAndAddWindows(),但是该方法是抽象方法,具体的实现是在PhoneStatusBar里面.

public void createAndAddWindows() {
addStatusBarWindow();
}
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = new StatusBarWindowManager(mContext);
if(mStatusBarWindowManager!=null){
mStatusBarWindowManager.setPhoneStatusBar(this);
}
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}

createAndAddWindows继续转调addStatusBarWindow (),addStatusBarWindow转调用makeStatusBarView()来进行构造系统状态栏的View和一些系统状态栏的监听的初始化.makeStatusBarView() 函数返回的是PhoneStatusBarView, PhoneStatusBarView主要是负责状态栏上面的一些Icon(wifi,sim,电池)和时间等,也就是说系统是在PhoneStatusBar.java里面来加载SystemUI的视图的.

1 SystemUI和StatusBarManagerService的交互

SystemServer.java -> main() - > run()-> startOtherServices().
SystemServer-startOtherServices()中部分代码:

if (!disableSystemUI) {
try {
Slog.i(TAG, "Status Bar");
statusBar = new StatusBarManagerService(context, wm);
ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
} catch (Throwable e) {
reportWtf("starting StatusBarManagerService", e);
}
}

上面看到获取了StatusBarManagerService对象并添加到了ServiceManager中,已Context.STATUS_BAR_SERVICE为标志,以便其他应用获取.然后分析StatusBarManagerService具体做了什么.

从StatusBarManagerService构造函数来看,

public class StatusBarManagerService extends IStatusBarService.Stub {
    private StatusBarIconList mIcons = new StatusBarIconList();
    .............
 
/**
* Construct the service, add the status bar view to the window manager
*/
public StatusBarManagerService(Context context, WindowManagerService windowManager) {
mContext = context;
mWindowManager = windowManager;
 
final Resources res = context.getResources();
mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));//这个config_statusBarIcons数组里面就是状态栏上面的图标
 
LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
 
/// M: DM Lock Feature.
registerDMLock();//mtk 添加的功能,具体不知道
}

StatusBarManagerService extends IStatusBarService.Stub 就是一个IBinder对象,Android AIDL 对这个有详细解释,它用来在进程传递,用来实现进程间的通信,

mIcons= new StatusBarIconList(); 这个StatusBarIconList 实现 Parcelable 接口,也就是序列化数据,用于序列化存储和传递数据和Java中的Serializable的某些情况下要高效.之所以要序列化是因为进程间传递复杂类型的数据需要进行序列化.

其中config_statusBarIcons的内容如下(路径:frameworks/base/core/res/res/values/config.xml)

总结下,在SystemServer进程中,维系了一个StatusBarManagerService对象statusBar,而statusBar对象维系了一个StatusBarIconList对象

mIcons,这样就实现了SystemServer 就维系了一个mIcons ,而这个mIcons存储了status bar中图标(String) 和 顺序 。

前面已经说过,PhoneStatusBar是SystemUI 的核心入口.

PhoneStatusBar.java

@Override
public void start() {
//...
super.start(); // calls createAndAddWindows()
//...
addNavigationBar();
 
// Lastly, call to the icon policy to install/update all the icons.
//注释上看,最终调用这个PhoneStatusBarPolicy.java来安装或者更新Icon
mIconPolicy = new PhoneStatusBarPolicy(mContext, mCastController, mHotspotController);
 
}
先来看看spuer.start方法,也就是BaseStatusBar的start()方法,这里先说一下,BaseStatusBar是个实现接口CommandQueue.Callbacks的抽象类,也就是说实际没有实现CommandQueue.Callbacks 的方法,需要子类PhoneStatusBar来实现

BaseStatusBar.java


 
public void start() {
//...
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
//..
// Connect in to the status bar manager service
StatusBarIconList iconList = new StatusBarIconList();
//传入了this,也就是说这个类或者子类实现了接口
mCommandQueue = new CommandQueue(this, iconList);
 
int[] switches = new int[8];
ArrayList<IBinder> binders = new ArrayList<IBinder>();
try {
mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
//...
// Set up the initial icon state
int N = iconList.size();
int viewIndex = 0;
for (int i=0; i<N; i++) {
StatusBarIcon icon = iconList.getIcon(i);//初始化图标的名字
if (icon != null) {
addIcon(iconList.getSlot(i), i, viewIndex, icon);
viewIndex++;
}
}
}
mCommandQueue = new CommandQueue(this,iconList);

CommandQueue extends IStatusBar.Stub 也就是一个IBinder对象,其中也定义了接口,不过接口实现是在PhoneStatusBar.java中

mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders); 这个mBarService就是StatusBarManagerService对象

 

CommandQueue.Callbacks


 
public interface Callbacks {
public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon);
public void updateIcon(String slot, int index, int viewIndex,
StatusBarIcon old, StatusBarIcon icon);
public void removeIcon(String slot, int index, int viewIndex);
public void disable(int state, boolean animate);
.........
public void notificationLightPulse(int argb, int onMillis, int offMillis);
public void showScreenPinningRequest();
/// M: [SystemUI] Support "SIM indicator". @{
public void showSimIndicator(String businessType);
public void hideSimIndicator();
.........
public void showRestoreButton(boolean flag);
}
@Override
public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
int switches[], List<IBinder> binders) {
enforceStatusBarService();
 
Slog.i(TAG, "registerStatusBar bar=" + bar);
mBar = bar;
synchronized (mIcons) {
//把mIcons的index和icon拷贝到iconList中,mIcons就是之前说到的那个数组转换来的
iconList.copyFrom(mIcons);
}
synchronized (mLock) {
switches[0] = gatherDisableActionsLocked(mCurrentUserId);
switches[1] = mSystemUiVisibility;
switches[2] = mMenuVisible ? 1 : 0;
switches[3] = mImeWindowVis;
switches[4] = mImeBackDisposition;
switches[5] = mShowImeSwitcher ? 1 : 0;
binders.add(mImeToken);
}
}

iconList.copyFrom(mIcons) 这里把StatusBarManagerService中维系的一个StatusBarIconList对象mIcons 复制给传进来的iconList中

这里只需要注意mBar,这到底有啥用呢,我们知道mBar是IStatusBar的对象 ,也就是CommandQueue的对象,

而CommandQueue的接口的实现方法是在PhoneStatusBar.java(加载StatusBar的界面)实现的,

所以就很好来控制界面 ,只要获取了StatusBarManagerService这个IBinder的相应的Binder驱动(如何获取,上面源码中有) ,

发送指令 -> StatusBarManagerService extends IStatusBarService.Stub 调用CommandQueue extends IStatusBar.Stub的接口

->实现在PhoneStatusBar.java(加载Status Bar的界面)中,就可以达到控制界面的目的,所以这个mBar很关键 ,后面要用到 .

这里我们才把icon的名字初始化完成,图标还没有设置,因为我们的iconList 只是在StatusBarManagerService里面将mIcon的内容拷贝过来了,图标还没有设置,现在需要调用CommandQueue.CallBack的addIcon来设置图标,但是BaseStatusBar本身是个抽象类,没有实现这个方法,具体的实现是在子类PhoneStatusBar里面的.

接着到PhoneStatusBar里面分析start(),之前已经提到里面有个PhoneStatusBarPolicy.看注释就知道是他负责更新icon的.

// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mCastController, mHotspotController);

PhoneStatusBarPolicy其实是通过注册了很多广播接受器来实现及时的更新这些icon的.具体代码如下.

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Xlog.d(TAG, "onReceive:" + action);
if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
updateAlarm();
}
else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) {
updateSyncState(intent);
}
else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) ||
action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
updateBluetooth();
}
else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) ||
action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) {
updateVolumeZen();
}
else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
updateSimState(intent);
}
else if (action.equals(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED)) {
int currentTtyMode = intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE,
TelecomManager.TTY_MODE_OFF);
updateTTY(currentTtyMode);
}
else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
updateAlarm();
/// M: [Multi-User] register Alarm intent by user @{
int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
registerAlarmClockChanged(newUserId, true);
/// M: [Multi-User] register Alarm intent by user @}
}
/// M: [SystemUI] Support "Headset icon". @{
else if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
updateHeadSet(intent);
}
/// @}
}
};
public PhoneStatusBarPolicy(Context context, CastController cast, HotspotController hotspot) {
mContext = context;
mCast = cast;
mHotspot = hotspot;
mService = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE);
// listen for broadcasts
IntentFilter filter = new IntentFilter();
//filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
/// M: [SystemUI] Support "Headset icon". @{
filter.addAction(Intent.ACTION_HEADSET_PLUG);
/// @}
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
/// M: [Multi-User] register Alarm intent by user
registerAlarmClockChanged(UserHandle.USER_OWNER, false);
// TTY status
mService.setIcon(SLOT_TTY, R.drawable.stat_sys_tty_mode, 0, null);
mService.setIconVisibility(SLOT_TTY, false);
/// M: [ALPS01870707] Get the TTY status when power on @{
int settingsTtyMode = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.TTY_MODE_ENABLED,
TelecomManager.TTY_MODE_OFF);
updateTTY(settingsTtyMode);
/// M: [ALPS01870707] Get the TTY status when power on @}
// Cdma Roaming Indicator, ERI
mService.setIcon(SLOT_CDMA_ERI, R.drawable.stat_sys_roaming_cdma_0, 0, null);
mService.setIconVisibility(SLOT_CDMA_ERI, false);
// bluetooth status
updateBluetooth();
// Alarm clock
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, false);
// Sync state
mService.setIcon(SLOT_SYNC_ACTIVE, R.drawable.stat_sys_sync, 0, null);
mService.setIconVisibility(SLOT_SYNC_ACTIVE, false);
// "sync_failing" is obsolete: b/1297963
 
// zen
mService.setIcon(SLOT_ZEN, R.drawable.stat_sys_zen_important, 0, null);
mService.setIconVisibility(SLOT_ZEN, false);
// volume
mService.setIcon(SLOT_VOLUME, R.drawable.stat_sys_ringer_vibrate, 0, null);
mService.setIconVisibility(SLOT_VOLUME, false);
updateVolumeZen();
// cast
// M: Remove CastTile when WFD is not support in quicksetting
if (mCast != null) {
mService.setIcon(SLOT_CAST, R.drawable.stat_sys_cast, 0, null);
mService.setIconVisibility(SLOT_CAST, false);
mCast.addCallback(mCastCallback);
}
// hotspot
mService.setIcon(SLOT_HOTSPOT, R.drawable.stat_sys_hotspot, 0, null);
mService.setIconVisibility(SLOT_HOTSPOT, mHotspot.isHotspotEnabled());
mHotspot.addCallback(mHotspotCallback);
/// M: [SystemUI] Support "Headset icon". @{
mService.setIcon(SLOT_HEADSET, R.drawable.stat_sys_headset_with_mic, 0, null);
mService.setIconVisibility(SLOT_HEADSET, false);
/// @}
}
public void setIcon(String slot, int iconId, int iconLevel, String contentDescription) {
try {
final IStatusBarService svc = getService();
if (svc != null) {
svc.setIcon(slot, mContext.getPackageName(), iconId, iconLevel,
contentDescription);
}
} catch (RemoteException ex) {
// system process is dead anyway.
throw new RuntimeException(ex);
}
}
private synchronized IStatusBarService getService() {
if (mService == null) {
mService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
if (mService == null) {
Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE");
}
}
return mService;
}

我们可以看到在构造方法中,注册了一系列的广播,然后通过广播来更新icon, 并在构造方法中初始化了icon以及可见性

mService.setIcon 和 mService.setIconVisibility 设置了Icon和Visibility , 注意,这里icon出现了

其中svc.setIcon其实就是调用StatusBarManagerService.java中setIcon,

@Override
public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
String contentDescription) {
enforceStatusBar();
 
synchronized (mIcons) {
int index = mIcons.getSlotIndex(slot);
if (index < 0) {
throw new SecurityException("invalid status bar icon slot: " + slot);
}
 
StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId,
iconLevel, 0,
contentDescription);
//Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
mIcons.setIcon(index, icon);
 
if (mBar != null) {
try {
mBar.setIcon(index, icon);
} catch (RemoteException ex) {
}
}
}
}

mBar.setIcon其实就是调用CommandQueue的setIcon.CommandQueue调用内部的Handler使用mCallBack来使用addIcon来将图标的图片设置到状态栏对应布局中去.而这个mCallBack其实就是实现了CommandQueue的CallBack接口.实际是PhoneStatusBar实现了这个addIcon方法.

PhoneStatusBar.java

public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
if (SPEW) Log.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
+ " icon=" + icon);
StatusBarIconView view = new StatusBarIconView(mContext, slot, null);
view.set(icon);
//add in mStatusIcons
mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, mIconSize));
view = new StatusBarIconView(mContext, slot, null);
view.set(icon);
mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, mIconSize));
}
  1. 下滑状态栏的响应流程
    首先我们要知道的是SystemUI 的状态栏再收起的时候,并不是说只有上面的状态栏那一部分才会调用相关的onTouchEvent方法的,实际上SystemUI 谁监听整个屏幕的,我们在桌面上面随便一个地方点击一下,com.android.systemui.statusbar.phone.StatusBarWindowView 都是会调用它的onTouchEvent方法的.这里先分析的是我们触碰一下状态栏哪里,状态栏是怎么样一步步的响应,最后拉下通知栏部分的.

    在前面SystemUI 启动章节就提到系统是在PhoneStatusBar.java里面来加载SystemUI的视图的.那就makeStatusBarView() 从开始. super_status_bar其实就是SystemUI 状态栏的第一个layout配置文件. StatusBarWindowView设置了touch事件处理器,其实最后还是调用的自己的onTouchEvent方法.如果onTouch()返回的false,那么就会执行StatusBarWindowView 的onTouchEvent().

PhoneStatusBarView就是屏幕上面的状态栏那一部分.我们下滑之后快捷开关设置部分和通知部分就是NotificationPanelView.

protected PhoneStatusBarView makeStatusBarView() {
final Context context = mContext;
.......
mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);
mStatusBarWindow.setBackground(null);
mStatusBarWindow.mService = this;
mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
checkUserAutohide(v, event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mExpandedVisible) {
animateCollapsePanels();
}
}
return mStatusBarWindow.onTouchEvent(event);
}});
 
mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
mStatusBarView.setBar(this);
 
holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder);
mStatusBarView.setPanelHolder(holder);//PhoneStatusBarView就是通过PanelHolder来获取NotificationPanelView对象的
 
mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
R.id.notification_panel);
mNotificationPanel.setStatusBar(this);
.....
 
}

从上面可以知道下滑动作的处理离不开PhoneStatusBarView 的.其实PhoneStatusBarview主要是把相关的MotionEvent时间传递给了NotificationPanelView来出来的.也就是直接调用NotificationPanelView的onTouchEvent().并没有执行NotificationPanelView的onInterceptTouchEvent().

这里需要特别说明的是PhoneStatusBarview的onInterceptTouchEvent()它始终返回的false,也就是说正常现象下是不应该执行本身的onTouchEvent()的,而是调用子视图的onTouch().当时这里却会调用PhoneStatusBarview本身的onTouchEvent().这是因为PhoneStatusBarview 所包含的child view视图并没有处理这些事件也就是是child view 的onTouchEvent() 都返回的是false.所以事件最终还是由PhoneStatusBarview来消费这个事件.

总结:在ViewGroup里面onInterceptTouchEvent里面如果反回false,触屏事件向下传递,但如果没有子View去消费这个事件,即子view的onTouchEvent没有返回True。

则最后还是由ViewGroup去消费此事件。也就又执行了自己的onTouchEvent.

PhoneStatusBarView就是通过PanelHolder来获取NotificationPanelView对象的,具体看上面的makeStatusBarView的红色部分.

因为NotificationPanelView本身就是PanelHolder的一个子视图.

在PhoneStatusBarView捕捉到ACTION_DWON事件的时候就已经将PanelHolder的子视图NotificationPanelView设置为自己的一个变量了.

PhoneStatusBarView.java 相关方法

public void addPanel(PanelView pv) {
mPanels.add(pv);
pv.setBar(this);
}
 
public void setPanelHolder(PanelHolder ph) {
if (ph == null) {
Log.e(TAG, "setPanelHolder: null PanelHolder", new Throwable());
return;
}
ph.setBar(this);
mPanelHolder = ph;
final int N = ph.getChildCount();
for (int i=0; i<N; i++) {
final View v = ph.getChildAt(i);
if (v != null && v instanceof PanelView) {
addPanel((PanelView) v);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
 
.........
// figure out which panel needs to be talked to here
if (event.getAction() == MotionEvent.ACTION_DOWN) {
final PanelView panel = selectPanelForTouch(event);
if (panel == null) {
// panel is not there, so we'll eat the gesture
Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)",
(int) event.getX(), (int) event.getY()));
mTouchingPanel = null;
return true;
}
boolean enabled = panel.isEnabled();
if (DEBUG) LOG("PanelBar.onTouch: state=%d ACTION_DOWN: panel %s %s", mState, panel,
(enabled ? "" : " (disabled)"));
if (!enabled) {
// panel is disabled, so we'll eat the gesture
Log.v(TAG, String.format(
"onTouch: panel (%s) is disabled, ignoring touch at (%d,%d)",
panel, (int) event.getX(), (int) event.getY()));
mTouchingPanel = null;
return true;
}
startOpeningPanel(panel);
}
final boolean result = mTouchingPanel != null
? mTouchingPanel.onTouchEvent(event)
: true;
return result;
}

上面的mTouchingPanel.onTouchEvent就是事件传递的过程.但是暂时没有搞明白为什么selectPanelForTouch方法里面为什么不是直接获取已经取到PanelView对象,因为实际就只有一个啊.不懂.

上面已经提到在下滑手指抬起的时候,就会调用fling()方法,Android L 原生的系统里面,下滑操作只会展开通知部分NotificationStackScrollLayout和StatusBarHeaderView部分,QSPanel部分默认是没有展开的.那就先看通知部分NotificationStackScrollLayout的展开流程.

2.1 通知部分NotificationStackScrollLayout的展开流程
在Panelview的ontouch方法里面判断是ACTION_UP之后,会根据滑动力度大小来调用fling,fling方法本身主要是设置并执行一个ValueAnimator.在ValueAnimator 变化过程中来实时调用setExpandedHeightInternal方法来展开NotificationStackScrollLayout.当然实际我们在判断是ACTION_MOVE也是会执行Panelview的setExpandedHeightInternal方法的

Panelview.java

public void setExpandedHeightInternal(float h) {
Log.d(TAG,"setExpandedHeightInternal finish() h=" + h + " mHeightAnimator=" + (mHeightAnimator==null));
float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
if (mHeightAnimator == null) {
float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
setOverExpansion(overExpansionPixels, true /* isPixels */);
}
mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
} else {
mExpandedHeight = h;
if (mOverExpandedBeforeFling) {
setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
}
}
 
mExpandedHeight = Math.max(0, mExpandedHeight);
mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
? 0
: mExpandedHeight / fhWithoutOverExpansion);
onHeightUpdated(mExpandedHeight);
notifyBarPanelExpansionChanged();
}

Panelview 的setExpandedHeightInternal()会转调其子类(NotificationPanelView)的onHeightUpdated()方法实现对NotificationStackScrollLayout的高度的修改来实现通知部分逐渐下滑.同时在QSContainer的位置改变的时候也会影响到通知部分(NotificationStackScrollLayout)的位置的改变.因为QSContainer 在初始化的时候就设置了OnLayoutChangeListener,在调用setQsTranslation来改变快捷设置部分(QSPanel)的位置和高度的时候是会导致这个接口被回调.

NotificationPanelView.java

@Override protected void onHeightUpdated(float expandedHeight) { Log.d(TAG,"onHeightUpdated expandedHeight=" + expandedHeight ); if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { positionClockAndNotifications(); } //部分代码去掉了....... mNotificationStackScroller.setStackHeight(expandedHeight); updateHeader(); updateUnlockIcon(); updateNotificationTranslucency(); }

其中NotificationStackScrollLayout和QSContainer 相关监听器都是在onFinishInflate()里面处理的.具体源码如下

@Override
protected void onFinishInflate() {
super.onFinishInflate();
.....
mNotificationStackScroller.setOnHeightChangedListener(this);
mNotificationStackScroller.setOverscrollTopChangedListener(this);
mNotificationStackScroller.setOnEmptySpaceClickListener(this);
mNotificationStackScroller.setScrollView(mScrollView);
....
 
// recompute internal state when qspanel height changes
mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight,
int oldBottom) {
final int height = bottom - top;
final int oldHeight = oldBottom - oldTop;
if (height != oldHeight) {
onScrollChanged();
}
}
});
}
@Override
public void onScrollChanged() {
if (mQsExpanded) {
requestScrollerTopPaddingUpdate(false /* animate */);
requestPanelHeightUpdate();
}
}
private void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
mScrollView.getScrollY(),
mAnimateNextTopPaddingChange || animate,
mKeyguardShowing
&& (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
mAnimateNextTopPaddingChange = false;
}
 

2.2 StatusBarHeaderView部分展开
这一部分就是下拉菜单展开之后显示时间和设置按钮的靠上面那一部分,其实这一部分的出来代码就是上面onHeightUpdated里面的的updateHeader方法.

NotificationPanelView.java

/**
* Hides the header when notifications are colliding with it.
*/
private void updateHeader() {
if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
|| mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
Log.d(TAG,"updateHeader ------if ");
updateHeaderKeyguard();
} else {
Log.d(TAG,"updateHeader ------else ");
updateHeaderShade();
}
private void updateHeaderShade() {
Log.d(TAG,"updateHeaderShade mHeaderAnimatingIn=" + mHeaderAnimatingIn + " mQsExpansionHeight=" + mQsExpansionHeight +
" getHeaderTranslation=" + getHeaderTranslation());
if (!mHeaderAnimatingIn) {
mHeader.setTranslationY(getHeaderTranslation());
}
setQsTranslation(mQsExpansionHeight);
}

updateHeader()就是根据实际是否解锁来调用不同的,如果解锁了就是调用updateHeaderShade来移动位置

现在我们想讨论的是下滑状态栏的时候直接展开快捷设置QSPanel和通知部分,所以我们先分析下滑的时候,SystemUI是如何处理我们的滑动事件的.
解锁之后的下滑状态栏栏流程.
解锁之后下滑状态栏的关键流程,前面有提到是从PhoneStatusBarview键touch事件由onInterceptTouchEvent返回false,而child view的onTouch又返回false,最近将事件传递给PhoneStatusBarview本身的onTouch来处理的.同样查看之前的流程图知道, PhoneStatusBarview的onTouch会调用NotificationPanelView的onTouch方法.

NotificationPanelView.java

public boolean onTouchEvent(MotionEvent event) {
if(DEBUG)Log.d(TAG , "onTouchEvent_npv action=" +event.getActionMasked());
if(getIsSuperSaveMode() || isSecurityMode()){//zhangle add
Log.d(TAG,"onTouchEvent IsSuperSaveMode" );
return false;
}
 
if (mBlockTouches) {
return false;
}
resetDownStates(event);
if ((!mIsExpanding || mHintAnimationRunning)
&& !mQsExpanded
&& mStatusBar.getBarState() != StatusBarState.SHADE) {
if(!doovDefaultLockView){
mAfforanceHelper.onTouchEvent(event);
}
}
if (mOnlyAffordanceInThisMotion) {
return true;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
&& mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
&& mQsExpansionEnabled) {
 
// Down in the empty area while fully expanded - go to QS.
mQsTracking = true;
mConflictingQsExpansionGesture = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = event.getX();
mInitialTouchX = event.getY();
}
 
mIsDown = mExpandedHeight > mLastExpandedHeight;
mLastExpandedHeight = mExpandedHeight;
//zhangle add end
 
if (mExpandedHeight != 0) {
handleQsDown(event);
}
 
if (!mQsExpandImmediate && mQsTracking) {//mQsTracking会在NotificationPanelView的onInterceptTouchEvent()里面处理Action_Move的时间变成true.
onQsTouch(event);
if (!mConflictingQsExpansionGesture) {
return true;
}
}
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
|| event.getActionMasked() == MotionEvent.ACTION_UP) {
mConflictingQsExpansionGesture = false;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0
&& mQsExpansionEnabled) {
mTwoFingerQsExpandPossible = true;
}
//这一段代码是为了实现默认滑动下拉可以完全展开通知栏自己添加的,后面再分析
if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_MOVE
&& event.getY(event.getActionIndex()) >= mStatusBarMinHeight && mIsExpanding) {
mQsExpandImmediate = true;
requestPanelHeightUpdate();
setListening(true);
}
 
if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
&& event.getPointerCount() == 2
&& event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
mQsExpandImmediate = true;
requestPanelHeightUpdate();
 
// Normally, we start listening when the panel is expanded, but here we need to start
// earlier so the state is already up to date when dragging down.
setListening(true);
}
 
super.onTouchEvent(event);//调用PanelView
return true;
}

查看以上代码可以看到,其实NotificationPanelView的onTouch()本身也是有处理.但是需要满足2个条件!mQsExpandImmediate&& mQsTracking.第一个变量先不说,直接说第2个,第2个mQsTracking需要在NotificationPanelView的onInterceptTouchEvent()里面的Move_Action才能是true.而我们目前讨论的情况是没有走onInterceptTouchEvent流程的,也就是说现在关键是super.onTouchEvent(event)这一行代码.这个也就是调用PanelView的onTouchEvent().

PanelView.java

public boolean onTouchEvent(MotionEvent event) {
if(DEBUG)Log.d(TAG , "onTouchEvent_pv action= " + event.getActionMasked() +(mInstantExpanding || mTouchDisabled));
if (mInstantExpanding || mTouchDisabled||(mStatusBar.getBarState() == StatusBarState.KEYGUARD)) {//解锁页面不处理相关事件
return false;
}
 
/*
* We capture touch events here and update the expand height here in case according to
* the users fingers. This also handles multi-touch.
*
* If the user just clicks shortly, we give him a quick peek of the shade.
*
* Flinging is also enabled in order to open or close the shade.
*/
 
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
 
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mGestureWaitForTouchSlop = mExpandedHeight == 0f;
}
boolean waitForTouchSlop = hasConflictingGestures() || mGestureWaitForTouchSlop;
 
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mInitialTouchY = y;
mInitialTouchX = x;
mInitialOffsetOnTouch = mExpandedHeight;
mTouchSlopExceeded = false;
mJustPeeked = false;
mPanelClosedOnDown = mExpandedHeight == 0.0f;
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
mPeekTouching = mPanelClosedOnDown;
mTouchAboveFalsingThreshold = false;
mDozingOnDown = isDozing();
if (mVelocityTracker == null) {
initVelocityTracker();
}
trackMovement(event);
if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) ||
mPeekPending || mPeekAnimator != null) {
cancelHeightAnimator();
cancelPeek();
mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
|| mPeekPending || mPeekAnimator != null;
onTrackingStarted();
}
if (mExpandedHeight == 0) {
schedulePeek();
}
break;
 
case MotionEvent.ACTION_POINTER_UP:
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
mTrackingPointer = event.getPointerId(newIndex);
mInitialOffsetOnTouch = mExpandedHeight;
mInitialTouchY = newY;
mInitialTouchX = newX;
}
break;
case MotionEvent.ACTION_MOVE:
float h = y - mInitialTouchY;
 
// If the panel was collapsed when touching, we only need to check for the
// y-component of the gesture, as we have no conflicting horizontal gesture.
if (Math.abs(h) > mTouchSlop
&& (Math.abs(h) > Math.abs(x - mInitialTouchX)
|| mInitialOffsetOnTouch == 0f)) {
mTouchSlopExceeded = true;
if (waitForTouchSlop && !mTracking) {
if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
mInitialOffsetOnTouch = mExpandedHeight;
mInitialTouchX = x;
mInitialTouchY = y;
h = 0;
}
cancelHeightAnimator();
removeCallbacks(mPeekRunnable);
mPeekPending = false;
onTrackingStarted();
}
}
final float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
if (newHeight > mPeekHeight) {
if (mPeekAnimator != null) {
mPeekAnimator.cancel();
}
mJustPeeked = false;
}
if (-h >= getFalsingThreshold()) {
mTouchAboveFalsingThreshold = true;
}
if (!mJustPeeked && (!waitForTouchSlop || mTracking) && !isTrackingBlocked()) {
Log.d(TAG , "onTouchEvent_move_newHeight="+newHeight);
setExpandedHeightInternal(newHeight);//手指在屏幕下移的时候调用的就是这里
}
 
trackMovement(event);
break;
 
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTrackingPointer = -1;
trackMovement(event);
if ((mTracking && mTouchSlopExceeded)
|| Math.abs(x - mInitialTouchX) > mTouchSlop
|| Math.abs(y - mInitialTouchY) > mTouchSlop
|| event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
float vel = 0f;
float vectorVel = 0f;
if (mVelocityTracker != null) {
mVelocityTrackerputeCurrentVelocity(1000);
vel = mVelocityTracker.getYVelocity();
vectorVel = (float) Math.hypot(
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
}
boolean expand = flingExpands(vel, vectorVel)
|| event.getActionMasked() == MotionEvent.ACTION_CANCEL;
onTrackingStopped(expand);
DozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
mStatusBar.isFalsingThresholdNeeded(),
mStatusBar.isScreenOnComingFromTouch());
// Log collapse gesture if on lock screen.
if (!expand && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
float displayDensity = mStatusBar.getDisplayDensity();
int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
int velocityDp = (int) Math.abs(vel / displayDensity);
EventLogTags.writeSysuiLockscreenGesture(
EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK,
heightDp, velocityDp);
}
fling(vel, expand);//滑动抬起手指时,自动下滑就是这里在处理
mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
if (mUpdateFlingOnLayout) {
mUpdateFlingVelocity = vel;
}
} else {
boolean expands = onEmptySpaceClick(mInitialTouchX);
onTrackingStopped(expands);
}
 
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mPeekTouching = false;
break;
}
return !waitForTouchSlop || mTracking;
}

上面这个方法我们需要关注就是返回值和Move,UP 事件.返回值是在3个事件(Action_Down,Action_Move,Action_UP)处理之后都是true,具体直接打log就可以看到.其中Action_Move的下拉实现是在setExpandedHeightInternal里面实现.Action_Up 的自动展开效果是在fling(vel, expand)里面实现的.但是fling(vel, expand)本身就是通过一个动画来实调用setExpandedHeightInternal.也就是ValueAnimator.AnimatorUpdateListener的onAnimationUpdate里面调用setExpandedHeightInternal.所以我们需要分析就是setExpandedHeightInternal这个方法, setExpandedHeightInternal 中需要我们注意的就是它调用了由子类实现的onHeightUpdated来转调setQsTranslation实现最终我们想要的视图位置的变动.

注意:

在fling(vel, expand)获取需要滑动的距离值target的时候,如果返回的值偏小就会出现默认不能自动展开的情况.具体为什么有时候获取的值偏小的根本原因暂时还没有搞明白.

锁屏页面的下滑状态栏栏流程.
实际分析下滑的log,很容易发现,具体走了NotificationPanelView的onInterceptTouchEvent(),也就是说和第一种情况是不一样的.

实际对于ViewGroup而已,每一个事件来之前,都是先经过dispatchTouchEvent.由于锁屏页面我们所看到的状态栏和解锁以后不一样.解锁之后的那个状态栏是layout/status_bar,而锁屏页面的是layout/keyguard_status_bar.

layout/keyguard_status_bar 是在layout/status_bar_expanded里面的.也就是NotificationPanelView的一个child view(KeyguardStatusBarView).

layout/status_bar 是PhoneStatusBarView也就是StatusBarWindowView的一个child view.

所以我们分析锁屏页面的下滑效果需要从NotificationPanelView 的dispatchTouchEvent开始.

NotificationPanelView 的dispatchTouchEvent只是调用了父类的dispatchTouchEvent的方法,所以最终还是要从NotificationPanelView 的onInterceptTouchEvent()来分析.

NotificationPanelView.java

public boolean onInterceptTouchEvent(MotionEvent event) {
if(DEBUG){
Log.d(TAG , "onInterceptTouchEvent_npv mBlockTouches=" + mBlockTouches + " event" + event.getAction());
}
 
if(getIsSuperSaveMode() || isSecurityMode()){//zhangle add
Log.d(TAG,"onInterceptTouchEvent IsSuperSaveMode" );
return false;
}
 
if (mBlockTouches) {
return false;
}
resetDownStates(event);
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
 
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mIntercepting = true;
mInitialTouchY = y;
mInitialTouchX = x;
initVelocityTracker();
trackMovement(event);
if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mQsExpansionAnimator != null) {
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
mQsTracking = true;
mIntercepting = false;
mNotificationStackScroller.removeLongPressCallback();
}
break;
case MotionEvent.ACTION_POINTER_UP:
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
mTrackingPointer = event.getPointerId(newIndex);
mInitialTouchX = event.getX(newIndex);
mInitialTouchY = event.getY(newIndex);
}
break;
 
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
trackMovement(event);
if (mQsTracking) {
 
// Already tracking because onOverscrolled was called. We need to update here
// so we don't stop for a frame until the next touch event gets handled in
// onTouchEvent.
setQsExpansion(h + mInitialHeightOnTouch);
trackMovement(event);
mIntercepting = false;
return true;
}
if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
//锁屏页面下滑时shouldQuickSettingsIntercept会返回true
mQsTracking = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = y;
mInitialTouchX = x;
mIntercepting = false;
mNotificationStackScroller.removeLongPressCallback();
return true;
}
break;
 
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
trackMovement(event);
if (mQsTracking) {
flingQsWithCurrentVelocity(
event.getActionMasked() == MotionEvent.ACTION_CANCEL);
mQsTracking = false;
}
mIntercepting = false;
break;
}
return super.onInterceptTouchEvent(event);
}

从Action_Down事件开始,但是好像没有什么特别,也没有看到返回值,但是需要注意的是其中对一个变量mQSTracking 赋值的条件,当时是mQsExpansionAnimator在我们正常下滑是null,也就是说Action_Down事件最终的返回值是false, KeyguardStatusBarView没有重写touch相关的处理方法.所以下一步还是看NotificationPanelView这个方法里面的Action_Move事件,这个事件会返回true,并修改mQSTracking为true.()

那接下来任何事件都是onTouchEvent()的工作了.由于之前一起贴出了这个方法全部代码,这里只贴出主要代码.

NotificationPanelView.java

public boolean onTouchEvent(MotionEvent event) {
........
if (!mQsExpandImmediate && mQsTracking) {//mQsTracking会在NotificationPanelView的onInterceptTouchEvent()里面处理Action_Move的时间变成true.
onQsTouch(event);
if (!mConflictingQsExpansionGesture) {
return true;
}
}
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
|| event.getActionMasked() == MotionEvent.ACTION_UP) {
mConflictingQsExpansionGesture = false;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0
&& mQsExpansionEnabled) {
mTwoFingerQsExpandPossible = true;
}
//这一段代码是为了实现默认滑动下拉可以完全展开通知栏自己添加的,后面再分析
if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_MOVE
&& event.getY(event.getActionIndex()) >= mStatusBarMinHeight && mIsExpanding) {
mQsExpandImmediate = true;
requestPanelHeightUpdate();
setListening(true);
}
........
 
super.onTouchEvent(event);//调用PanelView
return true;
}

onTouchEvent()->Action_Move:下面的代码是去掉不需要关注的部分之后的代码,!mQsExpandImmediate&& mQsTracking是为true的, mConflictingQsExpansionGesture是false,所以在这个方法内部调用onQsTouch之后就返回true了.而且不光光是这个Action_Move,后续的Action_Up事件也是一样的.很显然onQsTouch方法是核心了.

NotificationPanelView.java

private void onQsTouch(MotionEvent event) {
if(DEBUG)Log.d(TAG,"onQsTouch action=" + event.getActionMasked());
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
 
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
.....
case MotionEvent.ACTION_POINTER_UP:
.....
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
setQsExpansion(h + mInitialHeightOnTouch);
if (h >= getFalsingThreshold()) {
mQsTouchAboveFalsingThreshold = true;
}
trackMovement(event);
break;
 
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mQsTracking = false;
mTrackingPointer = -1;
trackMovement(event);
float fraction = getQsExpansionFraction();
if ((fraction != 0f || y >= mInitialTouchY)
&& (fraction != 1f || y <= mInitialTouchY)) {
flingQsWithCurrentVelocity(
event.getActionMasked() == MotionEvent.ACTION_CANCEL);
} else {
mScrollYOverride = -1;
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
}

onQsTouch()里面我们需要关注的只有 ACTION_MOVE和ACTION_UP这2种事件,其他暂时不贴出来.其中ACTION_MOVE是转调setQsExpansion ACTION_UP是转调flingQsWithCurrentVelocity,其中经过flingSettings之后也是通过动画效果来调用setQsExpansion,也就是最终和ACTION_MOVE类似.现在我们分析了解锁前面2种情况下下滑状态栏的操作之后发现其实ACTION_UP事件之后,其实都是在指定的范围内重复多次调用ACTION_MOVE的处理.setQSExpansion本身也是调用setQsTranslation来实现视图的位移的.

在NotificationPanelView.java的public void fling(float vel, boolean expand) 调用父类方法之前调用下面的slideDownQS(),其中变量mISDown只要在onTouchEvent()里面根据坐标来判断是否是滑动操作就可以了.

NotificationPanelView.java

private void slideDownloadQS() {
if(mIsExpanding && mQsExpansionEnabled && !mQsExpanded && mExpandedHeight!=0 && mIsDown){
Log.d(TAG , "slideDownloadQS ---");
setListening(true);
onQsExpansionStarted();
flingSettings(0 /* vel */, true /* expand */);
}
}
if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_MOVE
&& event.getY(event.getActionIndex()) >= mStatusBarMinHeight && mIsExpanding) {
mQsExpandImmediate = true;
requestPanelHeightUpdate();
setListening(true);
}
 

如果需要实现快捷设置部分顶部在下滑的时候不变位置.需要修改setQsTranslation,具体参考如下:

主要是对mQsContainer的处理.

private void setQsTranslation(float height) {
.........
if(DEBUG) Log.d(TAG,"setQsTranslation mHeaderAnimatingIn=" + mHeaderAnimatingIn );
if (!mHeaderAnimatingIn) {

//mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());

if(DEBUG)Log.d(TAG,"setQsTranslation height=" + height + " " + getHeaderTranslation() );
int heightOverride = (int) (mQsMaxExpansionHeight - height) ;
int y = mHeader.getCollapsedHeight()-heightOverride;
mQsContainer.setScrollY(-heightOverride);
mQsContainer.setY(y);
mQsContainer.invalidate();
if(DEBUG) Log.d(TAG,"setQsTranslation heightOverride=" + heightOverride + "y=" +y);
.......
}
.......
}
 

2.4按back键通知栏收起
当我们按back键的时候,会调用StatusBarWindowView 的dispatchKeyEvent().接着调用PhoneStatusBar相关方法.

public boolean dispatchKeyEvent(KeyEvent event) {
boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BACK:
if (!down) {
mService.onBackPressed();//mService就是PhoneStatusBar
}
return true;
case KeyEvent.KEYCODE_MENU:
if (!down) {
return mService.onMenuPressed();
}
case KeyEvent.KEYCODE_SPACE:
if (!down) {
return mService.onSpacePressed();
}
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
if (mService.isDozing()) {
MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, true);
return true;
}
break;
}
if (mService.interceptMediaKey(event)) {
return true;
}
return super.dispatchKeyEvent(event);
}

从上面的代码来看,实际还处理menu键和音量+ - 键.

2.5 按home键时状态栏的收起流程.
home 键的监听应用层一般都是监听Intent.ACTION_CLOSE_SYSTEM_DIALOGS 这个广播来实现的.SystemUI 也是采用的这个方法.

PhoneStatusBar.java

private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.v(TAG, "onReceive: " + intent);
if(DEBUG) Log.d(TAG, "onReceive:-------------- intent.getAction=" + intent.getAction());
String action = intent.getAction();
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {//监听home键
if (isCurrentProfile(getSendingUserId())) {
int flags = CommandQueue.FLAG_EXCLUDE_NONE;
String reason = intent.getStringExtra("reason");
if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
}
animateCollapsePanels(flags);
}
}
else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mScreenOn = false;
notifyNavigationBarScreenOn(false);
notifyHeadsUpScreenOn(false);
finishBarAnimations();
resetUserExpandedStates();
}
else if (Intent.ACTION_SCREEN_ON.equals(action)) {
mScreenOn = true;
notifyNavigationBarScreenOn(true);
}
else if (ACTION_DEMO.equals(action)) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
String command = bundle.getString("command", "").trim().toLowerCase();
if (command.length() > 0) {
try {
dispatchDemoCommand(command, bundle);
} catch (Throwable t) {
Log.w(TAG, "Error running demo command, intent=" + intent, t);
}
}
}
} else if ("fake_artwork".equals(action)) {
if (DEBUG_MEDIA_FAKE_ARTWORK) {
updateMediaMetaData(true);
}
}else if(LEATHER_PATTERN.equals(action)){
mHandler.removeMessages(MSG_LEATHER_PATTERN);
mHandler.sendEmptyMessage(MSG_LEATHER_PATTERN);
}
}
};

从上面可以看到,这里连近期任务的Home键也是有在上面做处理的,只是flag 标志位不一样.

PhoneStatusBar.java

public void animateCollapsePanels(int flags) {
animateCollapsePanels(flags, false /* force */);
}
 
public void animateCollapsePanels(int flags, boolean force) {
if (!force &&
(mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
runPostCollapseRunnables();
return;
}
if (SPEW) {
Log.d(TAG, "animateCollapse():"
+ " mExpandedVisible=" + mExpandedVisible
+ " flags=" + flags);
}
 
if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) {
if (!mHandler.hasMessages(MSG_HIDE_RECENT_APPS)) {
mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
}
}
 
if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) {
mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL);
mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL);
}
 
if (mStatusBarWindow != null) {
// release focus immediately to kick off focus change transition
mStatusBarWindowManager.setStatusBarFocusable(false);
 
mStatusBarWindow.cancelExpandHelper();
mStatusBarView.collapseAllPanels(true);//TODO:1
}
}

上面代码中实际需要我们关注的就是最好一行,这个PhoneStatusBarView. collapseAllPanels(true)会调用PanelView 的collapse()来转调fling()来实现收起状态栏等相关的动作.但是并不是到这里就结束了.

PanelBar.java

public void collapseAllPanels(boolean animate) {//case1:press home key -> animate=true
boolean waiting = false;
for (PanelView pv : mPanels) {
if (animate && !pv.isFullyCollapsed()) {//TODO
pv.collapse(true /* delayed */);//第一次是执行的这里
waiting = true;
} else {//后面会再次调用这个方法的时时候会传入参数为false,就执行的这里
pv.resetViews();
pv.setExpandedFraction(0); // just in case
pv.setVisibility(View.GONE);
pv.cancelPeek();
}
}
if (DEBUG) LOG("collapseAllPanels: animate=%s waiting=%s", animate, waiting);
Log.d(TAG,"collapseAllPanels animate=" +animate + " waiting=" + waiting + " mState=" + mState);
if (/*!waiting &&*/ mState != STATE_CLOSED) {//zhangle update
// it's possible that nothing animated, so we replicate the termination
// conditions of panelExpansionChanged here
go(STATE_CLOSED);
onAllPanelsCollapsed();
}
}

注意根据log来看,上面先执行pv.collapse(true/* delayed */),再执行onAllPanelsCollapsed().由于这里只是记录流程,暂时不具体分析为什么是执行的这2种case.

PanelView.java

 
public void collapse(boolean delayed) {
if (DEBUG) logf("collapse: " + this);
if (mPeekPending || mPeekAnimator != null) {
mCollapseAfterPeek = true;
if (mPeekPending) {
 
// We know that the whole gesture is just a peek triggered by a simple click, so
// better start it now.
removeCallbacks(mPeekRunnable);
mPeekRunnable.run()
}
} else if (!isFullyCollapsed() && !mTracking && !mClosing) {
cancelHeightAnimator();
mClosing = true;
notifyExpandingStarted();
if (delayed) {
postDelayed(mFlingCollapseRunnable, 120);
} else {
fling(0, false /* expand */);
}
}
}

但是需要我们注意的是PanelView 的collapse()里面执行collapse 的时候是使用postDelayed(mFlingCollapseRunnable, 120);来执行的,也是就是说是使用Handler来异步执行的,还有120ms的延迟,那么这个有可能会执行PanelBar.java的onAllPanelsCollapsed(),但是PanelBar.java对于这个方法并没有具体的实现内容,具体的实现是在PhoneStatusBarView.java里面的.

 
@Override
public void onAllPanelsCollapsed() {
super.onAllPanelsCollapsed();
 
// Close the status bar in the next frame so we can show the end of the animation.
postOnAnimation(new Runnable() {
@Override
public void run() {
mBar.makeExpandedInvisible();
}
});
mLastFullyOpenedPanel = null;
}

上面代码中的注释很明确,就是为了在关闭状态栏之前有一个动画效果给我们看到.那么接着就是查看PhoneStatusBar的makeExpandedInvisible(),会发现这个方法内容会再次调用PhoneStatusBarView的collapseAllPanels(),但是这次和之前在PhoneStatusBar 的animateCollapsePanels()调用主要的不同之处在于参数是false.这次执行collapseAllPanels()会执行里面的pv.setExpandedFraction(0)从而将PanelView的mExpandedHeight设置为0,其中这个变量在前面提到的fling()里面会用到,导致下面代码的第2个if判断条件会是ture.从而执行notifyExpandingFinished(),而不是以动画的方式来收起状态栏.

protected void fling(float vel, boolean expand) {
cancelPeek();
float target = expand ? getMaxPanelHeight() : 0.0f;
 
// Hack to make the expand transition look nice when clear all button is visible - we make
// the animation only to the last notification, and then jump to the maximum panel height so
// clear all just fades in and the decelerating motion is towards the last notification.
final boolean clearAllExpandHack = expand && fullyExpandedClearAllVisible()
&& mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
&& !isClearAllVisible();
final boolean isExpand = expand;//zhangle add
if (clearAllExpandHack) {
target = getMaxPanelHeight() - getClearAllHeight();
}
 
if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {//TODO
notifyExpandingFinished();
return;
}
//......
}
  1. 状态图标添加到状态栏的过程
    先从配置文件来分析,从super_status_bar.xml开始,这个是整个SystemUI应用的第一个布局配置文件.其中包括状态栏,下拉菜单,通知栏,幻灯片页面,锁屏页面.其中锁屏页面是在SystemUI 里面的,不是在Keyguard里面的,只有涉及到安全解锁比如密码相关等才在Keyguard里面的

super_status_bar.xml

 
<?xml version="1.0" encoding="utf-8"?>
 
<!-- This is the combined status bar / notification panel window. -->
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!--幻灯片-->
<com.android.systemui.statusbar.BackDropView
android:id="@+id/backdrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
>
<ImageView android:id="@+id/backdrop_back"
android:layout_width="match_parent"
android:scaleType="centerCrop"
android:layout_height="match_parent" />
<ImageView android:id="@+id/backdrop_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="invisible" />
</com.android.systemui.statusbar.BackDropView>
<!--这玩意没有见过-->
<com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_behind"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no" />
<!--状态栏部分-->
<include layout="@layout/status_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height" />
<!--调节亮度的部分-->
<FrameLayout android:id="@+id/brightness_mirror"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="wrap_content"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:paddingLeft="@dimen/notification_side_padding"
android:paddingRight="@dimen/notification_side_padding"
android:visibility="gone">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="2dp"
android:background="@drawable/brightness_mirror_background">
<include layout="@layout/quick_settings_brightness_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
</FrameLayout>
<!--状态栏下拉之后部分也是锁屏首页部分-->
<com.android.systemui.statusbar.phone.PanelHolder
android:id="@+id/panel_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent" >
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</com.android.systemui.statusbar.phone.PanelHolder>
 
<com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no" />
</com.android.systemui.statusbar.phone.StatusBarWindowView>

显然我们感兴趣的是状态栏部分和状态栏下拉部分,但是这一节分析的是状态栏的添加图标,所以只分析第一个部分.其中重点是layout/system_icons,因为这个里面讲主要显示在状态栏右侧的图标包含在里面的.

status_bar.xml

<com.android.systemui.statusbar.phone.PhoneStatusBarView
xmlns:android="http://schemas.android/apk/res/android"
xmlns:systemui="http://schemas.android/apk/res/com.android.systemui"
android:id="@+id/status_bar"
android:background="@drawable/system_bar_background"
android:orientation="vertical"
android:focusable="true"
android:descendantFocusability="afterDescendants"
>
 
<ImageView
android:id="@+id/notification_lights_out"
android:layout_width="@dimen/status_bar_icon_size"
android:layout_height="match_parent"
android:paddingStart="6dip"
android:paddingBottom="2dip"
android:src="@drawable/ic_sysbar_lights_out_dot_small"
android:scaleType="center"
android:visibility="gone"
/>
 
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="6dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
>
 
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal"
>
<!-- The alpha of this area is both controlled from PhoneStatusBarTransitions and
PhoneStatusBar (DISABLE_NOTIFICATION_ICONS), so we need two views here. -->
<com.android.keyguard.AlphaOptimizedLinearLayout
android:id="@+id/notification_icon_area_inner"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.android.systemui.statusbar.StatusBarIconView android:id="@+id/moreIcon"
android:layout_width="@dimen/status_bar_icon_size"
android:layout_height="match_parent"
android:src="@drawable/stat_notify_more"
android:visibility="gone"
/>
<com.android.systemui.statusbar.phone.IconMerger android:id="@+id/notificationIcons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:gravity="center_vertical"
android:orientation="horizontal"/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
 
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<!--sim卡,wifi,蓝牙,电池等系统图标部分,我们想要添加其他图标可以在这个里面添加-->
<include layout="@layout/system_icons" />
<!--时间部分-->
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
android:paddingStart="3dp"
android:gravity="center_vertical|start"
/>
<!-- zhangle update
android:paddingStart="7dp" to 3dp
-->
</com.android.keyguard.AlphaOptimizedLinearLayout>
</LinearLayout>
<!--应该是显示个人图像的部分-->
<ViewStub
android:id="@+id/ticker_stub"
android:inflatedId="@+id/ticker"
android:layout="@layout/status_bar_ticker"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
 
</com.android.systemui.statusbar.phone.PhoneStatusBarView>
layout/system_icons


 
<LinearLayout xmlns:android="http://schemas.android/apk/res/android"
android:id="@+id/system_icons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical">
 
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/statusIcons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"/>
<!--sim/wifi/vpn/飞行模式-->
<include layout="@layout/signal_cluster_view"
android:id="@+id/signal_cluster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"/>
<!--电池百分比数字-->
<TextView
android:id="@+id/battery_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="5dp"
android:visibility="invisible"
android:textColor="@color/status_bar_clock_color"
android:textSize="@dimen/battery_level_text_size"
android:theme="@style/shadow_doov"
android:gravity="center"/>
 
<!--电池进度比例图标-->
<!-- battery must be padded below to match assets -->
<com.android.systemui.BatteryMeterView android:id="@+id/battery"
android:layout_height="20dp"
android:layout_width="20dp"
android:rotation="-90"
android:gravity="center"
android:background="@color/shadow_color"
android:layout_gravity="center"/>
 
</LinearLayout>
@layout/signal_cluster_view


 
<com.android.systemui.statusbar.SignalClusterView
xmlns:android="http://schemas.android/apk/res/android"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingEnd="@dimen/signal_cluster_battery_padding"
>
<ImageView
android:id="@+id/vpn"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingEnd="6dp"
android:src="@drawable/stat_sys_vpn_ic"
/>
<FrameLayout
android:id="@+id/wifi_combo"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
>
<ImageView
android:id="@+id/wifi_signal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
</FrameLayout>
<View
android:id="@+id/wifi_signal_spacer"
android:layout_width="4dp"
android:layout_height="4dp"
android:visibility="gone"
/>
<!-- M: Support "SystemUI - VoLTE icon". @{ -->
<ImageView
android:id="@+id/volte_icon"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:visibility="gone"
/>
<!-- M: Support "SystemUI - VoLTE icon". }@ -->
<!-- M: Support "Default SIM Indicator". @{ -->
<ImageView
android:id="@+id/sim_indicator_internet_or_alwaysask"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginEnd="3dip"
/>
<!-- M: Support "Default SIM Indicator". }@ -->
<LinearLayout
android:id="@+id/mobile_signal_group"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
>
</LinearLayout>
<ImageView
android:id="@+id/no_sims"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/stat_sys_no_sims"
/>
<View
android:id="@+id/wifi_airplane_spacer"
android:layout_width="4dp"
android:layout_height="4dp"
android:visibility="gone"
/>
<ImageView
android:id="@+id/airplane"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
</com.android.systemui.statusbar.SignalClusterView>
以上都是配置文件,实际代码是在PhoneStatusBar.java里面的.相关代码如下,由于这个方法实在太长了,这里只是保留了一部分代码,也是为了清晰思路起见.

 


 
protected PhoneStatusBarView makeStatusBarView() {
mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);//加载第一个布局文件
 
mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);//加载状态栏布局文件
mStatusBarView.setBar(this);
 
holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder);//状态栏下拉部分
mStatusBarView.setPanelHolder(holder);
 
mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
R.id.notification_panel);//状态栏下拉部分
mNotificationPanel.setStatusBar(this);
 
mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area);
mSystemIcons = (LinearLayout) mStatusBarView.findViewById(R.id.system_icons);//系统图标
mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons);
mNotificationIconArea = mStatusBarView.findViewById(R.id.notification_icon_area_inner);
mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons);
mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon);
mNotificationIcons.setOverflowIndicator(mMoreIcon);
mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents);
//zhangle add for show battery level start
battery_percent = (TextView)mStatusBarView.findViewById(R.id.battery_percent);//电池百分比
battery_percent.setVisibility(getShowBatteryLevelStatus()?View.VISIBLE:View.GONE);
mBatteryLevelContentObserver = new BatteryLevelContentObserver(new Handler());
mBatteryLevelContentObserver.startObserver();
//zhangle add for show battery level end
mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
R.id.notification_stack_scroller);//下拉菜单通知部分
mStackScroller.setLongPressListener(getNotificationLongClicker());
mStackScroller.setPhoneStatusBar(this);
 
mEmptyShadeView = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_no_notifications, mStackScroller, false);
mStackScroller.setEmptyShadeView(mEmptyShadeView);//下拉菜单无通知
mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_dismiss_all, mStackScroller, false);//一键清除通知
mDismissView.setOnButtonClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clearAllNotifications();
}
});
 
mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);//下拉菜单Header 部分,就是有设置图标和时间那个部分
mHeader.setActivityStarter(this);
mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);//锁屏页面的状态栏
mStatusIconsKeyguard = (LinearLayout) mKeyguardStatusBar.findViewById(R.id.statusIcons);
mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view);
mKeyguardBottomArea =
(KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
 
mBatteryController = new BatteryController(mContext);//电池控制器
mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
@Override
public void onPowerSaveChanged() {
}
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
battery_percent.setText("" + level + "%");//zhangle add
}
});
mNetworkController = new NetworkControllerImpl(mContext);//网络控制器
mHotspotController = new HotspotControllerImpl(mContext);//热点控制器
mBluetoothController = new BluetoothControllerImpl(mContext, mHandlerThread.getLooper());//蓝牙控制器
mSecurityController = new SecurityControllerImpl(mContext);//安全控制器
 
if (SIMHelper.isMtkHotKnotSupport()) {
mHotKnotController = new HotKnotControllerImpl(mContext);//HotKnont控制器.mtk 自己研发的功能
} else {
mHotKnotController = null;
}
 
if (SIMHelper.isMtkAudioProfilesSupport()) {
mAudioProfileController = new AudioProfileControllerImpl(mContext);//情景模式控制器
} else {
mAudioProfileController = null;
}
 
if(!SIMHelper.isWifiOnlyDevice()) {
mDataConnectionController = new DataConnectionControllerImpl(mContext);//数据连接控制器
} else {
mDataConnectionController = null;
}
 
if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
mRotationLockController = new RotationLockControllerImpl(mContext);//屏幕旋转
}
mUserInfoController = new UserInfoController(mContext);//个人用户账号信息
mVolumeComponent = getComponent(VolumeComponent.class);//音量控制
if (mVolumeComponent != null) {
mZenModeController = mVolumeComponent.getZenController();//勿扰模式
}
 
return mStatusBarView;
}

一键清除通知
在下拉菜单判断有可以清除的通知的时候,在下方会出现一个清除的图标,也就是DismissView,点击这个DismissView,就会执行clearAllNotifications,接着转调performDismissAllAnimations来实现将需要隐藏的视图以动画的形式退出.

PhoneStatusBar.java

mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_dismiss_all, mStackScroller, false);
mDismissView.setOnButtonClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clearAllNotifications();
}
});
 


 
private void clearAllNotifications() {
 
// animate-swipe all dismissable notifications, then animate the shade closed
int numChildren = mStackScroller.getChildCount();
 
final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
for (int i = 0; i < numChildren; i++) {
final View child = mStackScroller.getChildAt(i);
if (mStackScroller.canChildBeDismissed(child)) {
if (child.getVisibility() == View.VISIBLE) {
viewsToHide.add(child);
}
}
}
if (viewsToHide.isEmpty()) {
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
return;
}
 
addPostCollapseAction(new Runnable() {
@Override
public void run() {
try {
mBarService.onClearAllNotifications(mCurrentUserId);
} catch (Exception ex) { }
}
});
 
performDismissAllAnimations(viewsToHide);
 
}
 

Notification 的排序
前面已经提到 PhoneStatusBar 的updateNotifications().其中通知的排序工作就是在这里调用mNotificationData.filterAndSort()来实现.准确的来时是调用Collections.sort(mSortedAndFiltered, mRankingComparator)来实现,其中mRankingComparator比较器代码如下.

比较的时候主要还是以priority 和时间先后来的.先考虑Notification的priority值大小,如果相同就比较Notification的when值大小.

当然音乐播放器相关的比较数,在有音乐应用运行的时候,音乐应用的等级要高一些.如果需要让其他应用高于他,可以参考下面的代码进行修改.

NotificationData.java

public void filterAndSort() {
mSortedAndFiltered.clear();
mGroupsWithSummaries.clear();
 
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
StatusBarNotification sbn = entry.notification;
 
if (shouldFilterOut(sbn)) {
continue;
}
 
if (sbn.getNotification().isGroupSummary()) {
mGroupsWithSummaries.add(sbn.getGroupKey());
}
mSortedAndFiltered.add(entry);
}
 
// Second pass: Filter out group children with summary.
if (!mGroupsWithSummaries.isEmpty()) {
final int M = mSortedAndFiltered.size();
for (int i = M - 1; i >= 0; i--) {
Entry ent = mSortedAndFiltered.get(i);
StatusBarNotification sbn = ent.notification;
if (sbn.getNotification().isGroupChild() &&
mGroupsWithSummaries.contains(sbn.getGroupKey())) {
mSortedAndFiltered.remove(i);
}
}
}
 
Collections.sort(mSortedAndFiltered, mRankingComparator);
}
 
private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
private final Ranking mRankingA = new Ranking();
private final Ranking mRankingB = new Ranking();
 
@Override
public int compare(Entry a, Entry b) {
// Upsort current media notification.
String mediaNotification = mEnvironment.getCurrentMediaNotificationKey();
boolean aMedia = a.key.equals(mediaNotification);
boolean bMedia = b.key.equals(mediaNotification);
 
//Doov zhangle add start:calendar can be higher than music apps
Log.d(TAG,"compare mediaNotification=" +mediaNotification + " a.key=" + a.key + " b.key=" + b.key);
if(aMedia || bMedia){
if(a.notification.getPackageName().equals("com.android.calendar") && a.notification.getNotification().priority >= Notification.PRIORITY_MAX ){
return -1;//b is media
}else if(b.notification.getPackageName().equals("com.android.calendar") && b.notification.getNotification().priority >= Notification.PRIORITY_MAX ){
return 1;//a is media
}
}//Doov zhangle add end:
 
if (aMedia != bMedia) {
return aMedia ? -1 : 1;
}
final StatusBarNotification na = a.notification;
final StatusBarNotification nb = b.notification;
 
// Upsort PRIORITY_MAX system notifications
boolean aSystemMax = na.getNotification().priority >= Notification.PRIORITY_MAX &&
isSystemNotification(na);
boolean bSystemMax = nb.getNotification().priority >= Notification.PRIORITY_MAX &&
isSystemNotification(nb);
if (aSystemMax != bSystemMax) {
return aSystemMax ? -1 : 1;
}
 
// RankingMap as received from NoMan.
if (mRankingMap != null) {
mRankingMap.getRanking(a.key, mRankingA);
mRankingMap.getRanking(b.key, mRankingB);
return mRankingA.getRank() - mRankingB.getRank();
}
 
int d = nb.getScore() - na.getScore();
if (a.interruption != b.interruption) {
return a.interruption ? -1 : 1;
} else if (d != 0) {
return d;
} else {
return (int) (nb.getNotification().when - na.getNotification().when);
}
}
};
 

4.4Heads Up Notification
   Heads Up Notification 是Android L里面新加的功能,需要这个功能.

Android L以下的的软件版本使用如下方法(需要导入support.v4的jar包)


 
notification=new NotificationCompat.Builder(MainActivity.this)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_launcher)
.setFullScreenIntent(pendingIntent, false)
.setContentTitle("这是标题")
.setContentText("这是内容")
.addAction(R.drawable.ic_launcher, "菜单1", peddingIntent1)
.build();
notificationManager.notify(1, notification);
 
    其中 Builder.setFullScreenIntent(pendingIntent, false) 是关键;
    setUsesChronometer (boolean ) 设置这个为 true 以后 heads-up会将 action 也显示出来

.

Android L 使用如下方法就可以了

Notification.Builder builder = new Notification.Builder(this);
builder.setVisibility(Notification.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_launcher)
.setFullScreenIntent(contentIntent, false)
.setPriority(Notification.PRIORITY_MAX)
.setContentTitle("这是标题")
.setContentText("这是内容")
.setDeleteIntent(contentIntent)
.addAction(R.drawable.ic_launcher, "菜单1", contentIntent)
.build();
nm.notify(1, builder.getNotification());

在实际使用的时候发现系统应用如果设置了setFullScreenIntent属性才能停留的久一点(10s)

以下代码中的mHeadsUpNotificationView.showNotification(interruptionCandidate)就是调用HeadsupNotification 的. showNotification 会接着调用scheduleHeadsUpOpen方法来进行处理.

相关代码如下

PhoneStatusBar.java

@Override
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
if (mUseHeadsUp && shouldInterrupt(notification)) {
if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
Entry interruptionCandidate = new Entry(notification, null);
ViewGroup holder = mHeadsUpNotificationView.getHolder();
if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
// 1. Populate mHeadsUpNotificationView
mHeadsUpNotificationView.showNotification(interruptionCandidate);
 
// do not show the notification in the shade, yet.
return;
}
}
 
//.....
}

mBar.scheduleHeadsUpOpen()表示弹窗操作,mBar.resetHeadsUpDecayTimer()表示关闭悬浮通知的操作.具体代码如下

public boolean showNotification(NotificationData.Entry headsUp) {
if (mHeadsUp != null && headsUp != null && !mHeadsUp.key.equals(headsUp.key)) {
// bump any previous heads up back to the shade
release();
}
 
mHeadsUp = headsUp;
if (mContentHolder != null) {
mContentHolder.removeAllViews();
}
 
if (mHeadsUp != null) {
mMostRecentPackageName = mHeadsUp.notification.getPackageName();
mHeadsUp.row.setSystemExpanded(true);
mHeadsUp.row.setSensitive(false);
mHeadsUp.row.setHeadsUp(true);
mHeadsUp.row.setHideSensitive(
false, false /* animated */, 0 /* delay */, 0 /* duration */);
if (mContentHolder == null) {
// too soon!
return false;
}
mContentHolder.setX(0);
mContentHolder.setVisibility(View.VISIBLE);
mContentHolder.setAlpha(mMaxAlpha);
mContentHolder.addView(mHeadsUp.row);
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
 
mSwipeHelper.snapChild(mContentHolder, 1f);
mStartTouchTime = SystemClock.elapsedRealtime() + mTouchSensitivityDelay;
 
mHeadsUp.setInterruption();
// 2. Animate mHeadsUpNotificationView in
mBar.scheduleHeadsUpOpen();
 
// 3. Set alarm to age the notification off
mBar.resetHeadsUpDecayTimer();
}
return true;
}

熄屏和亮屏
这里我们就Notifiter.java 的handleWakefulnessChange()开始分析.

亮屏时:mPolicy.wakingUp();

熄屏时:mPolicy.goingToSleep(why);

其实这里最终是调用的PhoneWindowManager.java 中的对应方法.

frameworks/base/services/core/java/com/android/server/power/Notifier.java

private void handleWakefulnessChange(final int wakefulness, boolean interactive,
final int reason) {
// Tell the activity manager about changes in wakefulness, not just interactivity.
// It needs more granularity than other components.
mHandler.post(new Runnable() {
@Override
public void run() {
mActivityManagerInternal.onWakefulnessChanged(wakefulness);
}
});
 
// Handle changes in the overall interactive state.
boolean interactiveChanged = false;
synchronized (mLock) {
// Broadcast interactive state changes.
if (interactive) {
// Waking up...
interactiveChanged = (mActualInteractiveState != INTERACTIVE_STATE_AWAKE);
if (interactiveChanged) {
mActualInteractiveState = INTERACTIVE_STATE_AWAKE;
mPendingWakeUpBroadcast = true;
mHandler.post(new Runnable() {
@Override
public void run() {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0);
if (DEBUG) {
Slog.d(TAG, "onInteractiveChangeStarted: mPolicy.wakingUp()");
}
mPolicy.wakingUp();//亮屏
}
});
updatePendingBroadcastLocked();
}
} else {
// Going to sleep...
// This is a good time to make transitions that we don't want the user to see,
// such as bringing the key guard to focus. There's no guarantee for this,
// however because the user could turn the device on again at any time.
// Some things may need to be protected by other mechanisms that defer screen on.
interactiveChanged = (mActualInteractiveState != INTERACTIVE_STATE_ASLEEP);
if (interactiveChanged) {
mActualInteractiveState = INTERACTIVE_STATE_ASLEEP;
mPendingGoToSleepBroadcast = true;
if (mUserActivityPending) {
mUserActivityPending = false;
mHandler.removeMessages(MSG_USER_ACTIVITY);
}
mHandler.post(new Runnable() {
@Override
public void run() {
int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER;
switch (reason) {
case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN;
break;
case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
why = WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT;
break;
case PowerManager.GO_TO_SLEEP_REASON_PROXIMITY:
why = WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR;
break;
}
EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0);
if (DEBUG) {
Slog.d(TAG, "mPolicy.goingToSleep: " + why);
}
mPolicy.goingToSleep(why);//熄屏
}
});
updatePendingBroadcastLocked();
}
}
}
 
// Notify battery stats.
if (interactiveChanged) {
try {
mBatteryStats.noteInteractive(interactive);
} catch (RemoteException ex) { }
}
}

熄屏
PhoneWindowManager的goingToSleep(int why)

以下时序图是熄屏时锁屏的处理流程.

上面的熄屏时序图中:

a.如果手机无操作待机熄屏和按power键熄屏:

KeyguardViewMediator就是走的

why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && (!lockImmediately && !mIsIPOShutDown))

WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT 对应的是手机无操作熄屏;

WindowManagerPolicy.OFF_BECAUSE_OF_USER 对应的按power键熄屏

b.如果是通话时距离感应器导致的熄屏:

这个情况是不做处理的.

亮屏
PhoneWindowManager 的wakingUp()开始

上面的图中的IKeyguardShowCallback需要在PhonewindowManger,java 看mKeyguardDelegateCallback 这个变量.

 
final ShowListener mKeyguardDelegateCallback = new ShowListener() {
@Override
public void onShown(IBinder windowToken) {
if (DEBUG_WAKEUP) Slog.d(TAG, "mKeyguardDelegate.ShowListener.onShown.");
mHandler.sendEmptyMessage(MSG_KEYGUARD_DRAWN_COMPLETE);
}
};
 

本文标签: 流程SystemUI