本文主要是介绍Android 11 SystemUI 启动流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
SystemUI 有哪内容
从表面上看, 我们看到的状态栏、通知栏、下拉菜单、导航栏、锁屏、最近任务、低电提示等系统页面都是 SystemUI 的。SystemUI,在源码目录中位于: framework/base/packages 目录下, 可见 SystemUI 和 framework 是关联的, SystemUI 依赖了很多内部 API , 系统资源, SystemUI 编译是要依赖系统源码的。
SystemUI 也是一个应用,不过这个应用特殊之处在于他没有启动图标、也没有入口 Activity 。他的入口程序是一个服务:SystemUIService。 这个服务会被系统服务拉起来, 这个服务起来, SystemUI 应用进程就创建起来了,具体启动过程后面会分析。除了 SystemUIService , SystemUI 还有很多服务, 例如: 负责锁屏的KeyguardService、负责最近任务的 RecentsSystemUserService、负责壁纸的 ImageWallpaper 、负责截屏的TakeScreenshotService 等。
下面是PhoneStatusBarView的view 树形图:
PanelHolder
PanelHolder是用户下拉 status bar 后得到的 view。它主要包含 QuickSettings 和 Notification panel 两个部分。PanelHolder是一个继承自FrameLayout的自定义view,它的内容是通过include status_bar_expanded.xml进行填充的。PanelHolder的布局比较复杂,为了提高view的重用性大量的使用了include标签。下面是PanelHolder的view树形图, 只给出了了主要的view:
架构关系
在系统服务中,有一个服务是专门为 SystemUI 的状态栏服务的, 这个服务就是 StatusbarManagerService (简称:SMS),和这个服务关系比较密切的服务是 WindowManagerService(简称:WMS), SMS 主要管控的是状态栏、导航栏, 例如:我们可以设置全屏、沉浸式状态栏都是 SMS 在起作用。
services组件启动时配置列表 : (R.array.config_systemUIServiceComponents)
所有 SystemUIService 都是继承自 SystemUI.class , SystemUI.class 是一个抽象类
<item>com.android.systemui.util.NotificationChannels</item> 通知信息 <item>com.android.systemui.keyguard.KeyguardViewMediator</item> 锁屏 <item>com.android.systemui.recents.Recents</item> 近期列表 Android 10之后近期列表的显示被移到Launcher里面了。在Launcher3的一个 类中TouchInteractionService.java IBinder mMyBinder = new IOverviewProxy.Stub() 通过AIDL的方法与systemUI通信 ———————————————— <item>com.android.systemui.volume.VolumeUI</item> 声音UI显示 <item>com.android.systemui.statusbar.phone.StatusBar</item> 状态栏及下拉面板 <item>com.android.systemui.usb.StorageNotification</item> usb通知管理 <item>com.android.systemui.power.PowerUI</item> 电源UI显示管理 <item>com.android.systemui.media.RingtonePlayer</item> 播放铃声 <item>com.android.systemui.keyboard.KeyboardUI</item>键盘UI <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>快捷方式 <item>@string/config_systemUIVendorServiceComponent</item>厂商相关定制 <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>垃圾监测器 <item>com.android.systemui.LatencyTester</item> 延迟测试仪 <item>com.android.systemui.globalactions.GlobalActionsComponent</item> 关机界面的显示、全局控制 <item>com.android.systemui.ScreenDecorations</item>屏幕装饰 <item>com.android.systemui.biometrics.AuthController</item>生物识别 <item>com.android.systemui.SliceBroadcastRelayHandler</item> 切片广播 <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> <item>com.android.systemui.theme.ThemeOverlayController</item> <item>com.android.systemui.accessibility.WindowMagnification</item> <item>com.android.systemui.accessibility.SystemActions</item> <item>com.android.systemui.toast.ToastUI</item> Toast <item>com.android.systemui.wmshell.WMShell</item>
一、SystemUI的启动流程
1、SystemServer
SystemServer启动后,会在Main Thread启动ActivityManagerService,当ActivityManagerService systemReady后,会去启动SystemUIService。
/frameworks/base/services/java/com/android/server/SystemServer.java
①main
/*** The main entry point from zygote.*/public static void main(String[] args) {new SystemServer().run();}
②run
private void run() {t.traceBegin("InitBeforeStartServices");....// Create the system service manager.mSystemServiceManager = new SystemServiceManager(mSystemContext);mSystemServiceManager.setStartInfo(mRuntimeRestart,mRuntimeStartElapsedTime, mRuntimeStartUptime);LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);....}
③mActivityManagerService.systemReady
mActivityManagerService.systemReady(() -> {//准备好服务Slog.i(TAG, "Making services ready");....//跟踪开启系统界面t.traceBegin("StartSystemUI");try {//开启系统界面startSystemUi(context, windowManagerF);} catch (Throwable e) {reportWtf("starting System UI", e);}t.traceEnd();.... }
④startSystemUi
private static void startSystemUi(Context context, WindowManagerService windowManager) {PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);Intent intent = new Intent();intent.setComponent(pm.getSystemUiServiceComponent());intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);//Slog.d(TAG, "Starting service: " + intent);//通过startServiceAsUser,SystemUIService就启动了,即SystemUI进程开机启动context.startServiceAsUser(intent, UserHandle.SYSTEM);windowManager.onSystemUiStarted();}
2、systemUIService
在SystemUIService的onCreate方法中会调用SystemUIApplication的startServicesIfNeeded方法,这个方法会调用 startServicesIfNeeded(SERVICES)方法启动一系列服务
@Overridepublic void onCreate() {super.onCreate();// Start all of SystemUI((SystemUIApplication) getApplication()).startServicesIfNeeded();
3、SystemUIApplication
/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
①startServicesIfNeeded()
public void startServicesIfNeeded() {//获取所有的服务的路径,所有SERVICES统一继承了SystemUI类:String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names); }
②startServicesIfNeeded(String metricsPrefix, String[] services)
在重载方法中将每一个名称通过反射来得到实例对象,然后依次调用每一个SystemUI的子类的start方法启动每一个模块。
private void startServicesIfNeeded(String metricsPrefix, String[] services) {if (mServicesStarted) {return;}mServices = new SystemUI[services.length];//检查一下,也许在我们开始之前很久它就已经完成了if (!mBootCompleteCache.isBootComplete()) {// check to see if maybe it was already completed long before we began// see ActivityManagerService.finishBooting()if ("1".equals(SystemProperties.get("sys.boot_completed"))) {mBootCompleteCache.setBootComplete();if (DEBUG) {Log.v(TAG, "BOOT_COMPLETED was already sent");}}}final DumpManager dumpManager = mRootComponent.createDumpManager();Log.v(TAG, "Starting SystemUI services for user " +Process.myUserHandle().getIdentifier() + ".");TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",Trace.TRACE_TAG_APP);//开始追踪log.traceBegin(metricsPrefix);final int N = services.length;//遍历services这个数组for (int i = 0; i < N; i++) {String clsName = services[i];if (DEBUG) Log.d(TAG, "loading: " + clsName);log.traceBegin(metricsPrefix + clsName);long ti = System.currentTimeMillis();try {SystemUI obj = mComponentHelper.resolveSystemUI(clsName);if (obj == null) {Constructor constructor = Class.forName(clsName).getConstructor(Context.class);obj = (SystemUI) constructor.newInstance(this);}mServices[i] = obj;} catch (ClassNotFoundException| NoSuchMethodException| IllegalAccessException| InstantiationException| InvocationTargetException ex) {throw new RuntimeException(ex);}if (DEBUG) Log.d(TAG, "running: " + mServices[i]);//依次调用service的start方法启动服务mServices[i].start();log.traceEnd();// Warn if initialization of component takes too long//如果组件初始化时间过长,则发出警告ti = System.currentTimeMillis() - ti;if (ti > 1000) {Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");}if (mBootCompleteCache.isBootComplete()) {mServices[i].onBootCompleted();}dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);}mRootComponent.getInitController().executePostInitTasks();//结束追踪log.traceEnd();mServicesStarted = true; }
4、SystemUI
/*** @see SystemUIApplication#startServicesIfNeeded()*系统界面应用 如果需要,启动服务*/ public abstract class SystemUI implements Dumpable {protected final Context mContext;public SystemUI(Context context) {mContext = context;}public abstract void start();protected void onConfigurationChanged(Configuration newConfig) {}@Overridepublic void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {}protected void onBootCompleted() {}public static void overrideNotificationAppName(Context context, Notification.Builder n,boolean system) {final Bundle extras = new Bundle();String appName = system? context.getString(com.android.internal.R.string.notification_app_name_system): context.getString(com.android.internal.R.string.notification_app_name_settings);extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName);n.addExtras(extras);} }
二、状态栏
1、SystemBars
SystemBars加载基本全部SystemUI的界面显示,由前面可知调用的start方法实际上是每一个继承于SytemUI的子类中的方法
①start
②createStatusBarFromConfig
从string资源文件里面读取class name,通过java的反射机制实例化对象,然后调用start()方法启动,class name的值如下图:
private void createStatusBarFromConfig() {......String clsName = mContext.getString(R.string.config_statusBarComponent);......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.start();......}
③String
<!-- Component to be used as the status bar service. Must implement the IStatusBarinterface. This name is in the ComponentName flattened format (package/class) --><string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
2、StatusBar
①createAndAddWindows
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {//创建状态栏makeStatusBarView(result);mNotificationShadeWindowController.attach();//创建状态栏的窗口mStatusBarWindowController.attach();}
②makeStatusBarView
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {final Context context = mContext;.....FragmentHostManager.get(mPhoneStatusBarWindow).addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {//CollapsedStatusBarFragment 替换 status_bar_container(状态栏通知显示区域)CollapsedStatusBarFragment statusBarFragment =(CollapsedStatusBarFragment) fragment;PhoneStatusBarView oldStatusBarView = mStatusBarView;mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();//传递statusBar处理下拉事件mStatusBarView.setBar(this);//传递 NotificationPanelView 显示下拉UI控制mStatusBarView.setPanel(mNotificationPanelViewController);mStatusBarView.setScrimController(mScrimController);//初始化通知栏区域statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);......}).getFragmentManager().beginTransaction().replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),CollapsedStatusBarFragment.TAG).commit();.....
2、CollapsedStatusBarFragment
①onCreateView
@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,Bundle savedInstanceState) {return inflater.inflate(R.layout.status_bar, container, false);}
②initNotificationIconArea
public void initNotificationIconArea(NotificationIconAreaControllernotificationIconAreaController) {//notification_icon_area是在status_bar.xml的布局,它是属于通知Notification //获取到 notification_icon_area,FrameLayout转为ViewGroup,ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);//调用 notificationIconAreaController 获取通知要显示的view(LinearLayout)//在4中跟进mNotificationIconAreaInner =notificationIconAreaController.getNotificationInnerAreaView();//如果已经有显示的view,通过 view 父布局将其自身remove,然后再重新addView。//最后将 mNotificationIconAreaInner 显示出来(设置透明度为1,visibility为VISIBLE)if (mNotificationIconAreaInner.getParent() != null) {((ViewGroup) mNotificationIconAreaInner.getParent()).removeView(mNotificationIconAreaInner);}notificationIconArea.addView(mNotificationIconAreaInner);//与上面一样ViewGroup statusBarCenteredIconArea = mStatusBar.findViewById(R.id.centered_icon_area);mCenteredIconArea = notificationIconAreaController.getCenteredNotificationAreaView();if (mCenteredIconArea.getParent() != null) {((ViewGroup) mCenteredIconArea.getParent()).removeView(mCenteredIconArea);}statusBarCenteredIconArea.addView(mCenteredIconArea);//默认为显示,直到我们知道其他情况// Default to showing until we know otherwise.showNotificationIconArea(false); }
③showNotificationIconArea
当状态栏下拉时,状态栏中的图标icon会慢慢的变成透明和不可见,就是通过hideSystemIconArea(true), hideNotificationIconArea(true)
//当状态栏下拉时,设置状态栏中的图标icon会慢慢的变成透明和不可见 public void hideNotificationIconArea(boolean animate) {animateHide(mNotificationIconAreaInner, animate);animateHide(mCenteredIconArea, animate);} //设置状态栏图标透明度为1,visibility为VISIBLEpublic void showNotificationIconArea(boolean animate) {animateShow(mNotificationIconAreaInner, animate);animateShow(mCenteredIconArea, animate);}public void hideOperatorName(boolean animate) {if (mOperatorNameFrame != null) {animateHide(mOperatorNameFrame, animate);}}public void showOperatorName(boolean animate) {if (mOperatorNameFrame != null) {animateShow(mOperatorNameFrame, animate);}
④animateShow
private void animateShow(View v, boolean animate) {v.animate().cancel();//(设置透明度为1,visibility为VISIBLE)v.setVisibility(View.VISIBLE);if (!animate) {v.setAlpha(1f);return;}.....}
⑤animateHiddenState
//将视图动画化为 INVISIBLE 或 GONE private void animateHiddenState(final View v, int state, boolean animate) {v.animate().cancel();if (!animate) {v.setAlpha(0f);v.setVisibility(state);return;}v.animate().alpha(0f).setDuration(160).setStartDelay(0).setInterpolator(Interpolators.ALPHA_OUT).withEndAction(() -> v.setVisibility(state));}
3、status_bar.xml
<?xml version="1.0" encoding="utf-8"?> <!-- ** ** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ --><!-- android:background="@drawable/status_bar_closed_default_background" --> <com.android.systemui.statusbar.phone.PhoneStatusBarViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"android:layout_width="match_parent"android:layout_height="@dimen/status_bar_height"android:id="@+id/status_bar"android:orientation="vertical"android:focusable="false"android:descendantFocusability="afterDescendants"android:accessibilityPaneTitle="@string/status_bar"><!-- add for KGDAANWIKFRA-135 --><Viewandroid:layout_width="match_parent"android:layout_height="@dimen/status_bar_height"android:id="@+id/status_bar_dark_view"android:background="#ff000000"android:visibility="gone" />//<!--通知灯,默认gone--><ImageViewandroid:id="@+id/notification_lights_out"android:layout_width="@dimen/status_bar_icon_size"android:layout_height="match_parent"android:paddingStart="@dimen/status_bar_padding_start"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="@dimen/status_bar_padding_start"android:paddingEnd="@dimen/status_bar_padding_end"android:paddingTop="@dimen/status_bar_padding_top"android:orientation="horizontal"><FrameLayoutandroid:layout_height="match_parent"android:layout_width="0dp"android:layout_weight="1"><include layout="@layout/heads_up_status_bar_layout" /><!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and theindividual views are controlled by StatusBarManager disable flags DISABLE_CLOCK andDISABLE_NOTIFICATION_ICONS, respectively --><LinearLayoutandroid:id="@+id/status_bar_left_side"android:layout_height="match_parent"android:layout_width="match_parent"android:clipChildren="false"><ViewStubandroid:id="@+id/operator_name"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout="@layout/operator_name" /><com.android.systemui.statusbar.policy.Clockandroid:id="@+id/clock"android:layout_width="wrap_content"android:layout_height="match_parent"android:textAppearance="@style/TextAppearance.StatusBar.Clock"android:singleLine="true"android:paddingStart="@dimen/status_bar_left_clock_starting_padding"android:paddingEnd="@dimen/status_bar_left_clock_end_padding"android:gravity="center_vertical|start"/>//<!--通知图标区域--><com.android.systemui.statusbar.AlphaOptimizedFrameLayoutandroid:id="@+id/notification_icon_area"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:orientation="horizontal"android:clipChildren="false"/></LinearLayout></FrameLayout><!-- Space should cover the notch (if it exists) and let other views lay out around it --><android.widget.Spaceandroid:id="@+id/cutout_space_view"android:layout_width="0dp"android:layout_height="match_parent"android:gravity="center_horizontal|center_vertical"/>//居中的图标区域 <com.android.systemui.statusbar.AlphaOptimizedFrameLayoutandroid:id="@+id/centered_icon_area"android:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"android:clipChildren="false"android:gravity="center_horizontal|center_vertical"/><com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:orientation="horizontal"android:gravity="center_vertical|end">//<!--系统图标--><include layout="@layout/system_icons" /></com.android.keyguard.AlphaOptimizedLinearLayout></LinearLayout>//<!--紧急密码管理员文本--><ViewStubandroid:id="@+id/emergency_cryptkeeper_text"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout="@layout/emergency_cryptkeeper_text"/></com.android.systemui.statusbar.phone.PhoneStatusBarView>
4、NotificationIconAreaController
①getNotificationInnerAreaView
/*** Returns the view that represents the notification area.+* 返回表示通知区域的视图。*/public View getNotificationInnerAreaView() {return mNotificationIconArea;}
②initializeNotificationAreaViews
/*** Initializes the views that will represent the notification area.* 初始化将表示通知区域的视图。*/ protected void initializeNotificationAreaViews(Context context) {reloadDimens(context);LayoutInflater layoutInflater = LayoutInflater.from(context);//通知图标区域布局mNotificationIconArea = inflateIconArea(layoutInflater);//通知图标mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);//获取通知滚动布局mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();//中心图标区域布局mCenteredIconArea = layoutInflater.inflate(R.layout.center_icon_area, null);//居中的图标mCenteredIcon = mCenteredIconArea.findViewById(R.id.centeredIcon);initAodIcons(); }
③inflateIconArea
protected View inflateIconArea(LayoutInflater inflater) {return inflater.inflate(R.layout.notification_icon_area, null);}
三、status icon加载流程
1、 status_bar.xml
<com.android.systemui.statusbar.AlphaOptimizedFrameLayoutandroid:id="@+id/centered_icon_area"android:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"android:clipChildren="false"android:gravity="center_horizontal|center_vertical"/><com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:orientation="horizontal"android:gravity="center_vertical|end"><!--系统图标--><include layout="@layout/system_icons" /></com.android.keyguard.AlphaOptimizedLinearLayout>
2、system_icons.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:id="@+id/system_icons"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center_vertical">//StatusIconContainer继承AlphaOptimizedLinearLayout<com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"android:layout_width="0dp"android:layout_weight="1"android:layout_height="match_parent"android:paddingEnd="@dimen/signal_cluster_battery_padding"android:gravity="center_vertical"android:orientation="horizontal"/><com.android.systemui.BatteryMeterView android:id="@+id/battery"android:layout_height="match_parent"android:layout_width="wrap_content"android:clipToPadding="false"android:clipChildren="false"systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" /> </LinearLayout>
3、AlphaOptimizedLinearLayout
//该方法用来标记当前view是否存在过度绘制,存在返回ture,不存在返回false,//api里面默认返回为true,status icon不存在过度绘制。@Overridepublic boolean hasOverlappingRendering() {return false;}
4、CollapsedStatusBarFragment
@Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);mStatusBar = (PhoneStatusBarView) view;if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {mStatusBar.restoreHierarchyState(savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));} ......}
4、StatusBarIconController
①DarkIconManager
/*** Version of ViewGroup that observes state from the DarkIconDispatcher.*/public static class DarkIconManager extends IconManager {private final DarkIconDispatcher mDarkIconDispatcher;private int mIconHPadding;public DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue) {super(linearLayout, commandQueue);mIconHPadding = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_padding);mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);}//每个icon应该就是对应着代表顺序的index和数据类型为String的slot@Overrideprotected void onIconAdded(int index, String slot, boolean blocked,StatusBarIconHolder holder) {StatusIconDisplayable view = addHolder(index, slot, blocked, holder);mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);}.....//onSetIcon可能就是刷新icon状态的@Overridepublic void onSetIcon(int viewIndex, StatusBarIcon icon) {super.onSetIcon(viewIndex, icon);mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex));}.....}
②IconManager
public static class IconManager implements DemoMode {.....protected void onIconAdded(int index, String slot, boolean blocked,StatusBarIconHolder holder) {addHolder(index, slot, blocked, holder);} 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;}@VisibleForTestingprotected StatusBarIconView addIcon(int index, String slot, boolean blocked,StatusBarIcon icon) {StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);view.set(icon);mGroup.addView(view, index, onCreateLayoutParams());return view;}..... }
5、StatusBarIconControllerImpl
//继承StatusBarIconList public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController { ...... @Injectpublic StatusBarIconControllerImpl(Context context, CommandQueue commandQueue) {//config_statusBarIconssuper(context.getResources().getStringArray(com.android.internal.R.array.config_statusBarIcons));Dependency.get(ConfigurationController.class).addCallback(this);.....} }
6、StatusBarIconList
在初始化的时候就已经定义好了所有的slots,然后从framework中加载出来,index就是string-array中的顺序。
public class StatusBarIconList {private ArrayList<Slot> mSlots = new ArrayList<>();public StatusBarIconList(String[] slots) {final int N = slots.length;for (int i=0; i < N; i++) {mSlots.add(new Slot(slots[i], null));}}
7、config_statusBarIcons
<string-array name="config_statusBarIcons"><item><xliff:g id="id">@string/status_bar_rotate</xliff:g></item><item><xliff:g id="id">@string/status_bar_headset</xliff:g></item><item><xliff:g id="id">@string/status_bar_data_saver</xliff:g></item><item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item><item><xliff:g id="id">@string/status_bar_ime</xliff:g></item><item><xliff:g id="id">@string/status_bar_sync_failing</xliff:g></item><item><xliff:g id="id">@string/status_bar_sync_active</xliff:g></item><item><xliff:g id="id">@string/status_bar_cast</xliff:g></item><item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item><item><xliff:g id="id">@string/status_bar_location</xliff:g></item><item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item><item><xliff:g id="id">@string/status_bar_nfc</xliff:g></item><item><xliff:g id="id">@string/status_bar_tty</xliff:g></item><item><xliff:g id="id">@string/status_bar_speakerphone</xliff:g></item><item><xliff:g id="id">@string/status_bar_zen</xliff:g></item><item><xliff:g id="id">@string/status_bar_mute</xliff:g></item><item><xliff:g id="id">@string/status_bar_volume</xliff:g></item><item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item><item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item><item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item><item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item><item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item><item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item><item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item><item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item><item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item><item><xliff:g id="id">@string/status_bar_battery</xliff:g></item><item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item><item><xliff:g id="id">@string/status_bar_secure</xliff:g></item><item><xliff:g id="id">@string/status_bar_clock</xliff:g></item></string-array><string translatable="false" name="status_bar_rotate">rotate</string><string translatable="false" name="status_bar_headset">headset</string><string translatable="false" name="status_bar_data_saver">data_saver</string><string translatable="false" name="status_bar_managed_profile">managed_profile</string><string translatable="false" name="status_bar_ime">ime</string><string translatable="false" name="status_bar_sync_failing">sync_failing</string><string translatable="false" name="status_bar_sync_active">sync_active</string><string translatable="false" name="status_bar_cast">cast</string><string translatable="false" name="status_bar_hotspot">hotspot</string><string translatable="false" name="status_bar_location">location</string><string translatable="false" name="status_bar_bluetooth">bluetooth</string><string translatable="false" name="status_bar_nfc">nfc</string><string translatable="false" name="status_bar_tty">tty</string><string translatable="false" name="status_bar_speakerphone">speakerphone</string><string translatable="false" name="status_bar_zen">zen</string><string translatable="false" name="status_bar_mute">mute</string><string translatable="false" name="status_bar_volume">volume</string><string translatable="false" name="status_bar_wifi">wifi</string><string translatable="false" name="status_bar_cdma_eri">cdma_eri</string><string translatable="false" name="status_bar_data_connection">data_connection</string><string translatable="false" name="status_bar_phone_evdo_signal">phone_evdo_signal</string><string translatable="false" name="status_bar_phone_signal">phone_signal</string><string translatable="false" name="status_bar_battery">battery</string><string translatable="false" name="status_bar_alarm_clock">alarm_clock</string><string translatable="false" name="status_bar_secure">secure</string><string translatable="false" name="status_bar_clock">clock</string><string translatable="false" name="status_bar_mobile">mobile</string><string translatable="false" name="status_bar_vpn">vpn</string><string translatable="false" name="status_bar_ethernet">ethernet</string><string translatable="false" name="status_bar_airplane">airplane</string>
好了,到这里我们的第一部分初始化流程就讲完了
四、状态显示流程
由上面的初始化流程我们可以知道,每个icon都对应了slot,slot数量比较多,我们就挑一个常见的Headset讲下,其他的流程都是大致一样的。
1、PhoneStatusBarPolicy
初始化注册了大量的监听
①init
// 初始化headset的slot mSlotHeadset = resources.getString(com.android.internal.R.string.status_bar_headset); /** Initialize the object after construction. */ public void init() {// listen for broadcastsIntentFilter filter = new IntentFilter();// 注册headset状态变化的actionfilter.addAction(AudioManager.ACTION_HEADSET_PLUG);filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen);mRingerModeTracker.getRingerMode().observeForever(observer);mRingerModeTracker.getRingerModeInternal().observeForever(observer);....}
②BroadcastReceiver
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();switch (action) {case Intent.ACTION_SIM_STATE_CHANGED:// Avoid rebroadcast because SysUI is direct boot aware.if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {break;}break;case TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED:updateTTY(intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE,TelecomManager.TTY_MODE_OFF));break;case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:case Intent.ACTION_MANAGED_PROFILE_REMOVED:updateManagedProfile();break;//监听ACTION_HEADSET_PLUGcase AudioManager.ACTION_HEADSET_PLUG:updateHeadsetPlug(context, intent);break;}}};
③updateHeadsetPlug
完成icon添加和状态监听,然后当收到对应的action变化的时候,更新headset icon
private void updateHeadsetPlug(Context context, Intent intent) {boolean connected = intent.getIntExtra("state", 0) != 0;boolean hasMic = intent.getIntExtra("microphone", 0) != 0;if (connected) {String contentDescription = mResources.getString(hasMic? R.string.accessibility_status_bar_headset: R.string.accessibility_status_bar_headphones);//setIcon负责设置iconmIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.stat_sys_headset_mic: R.drawable.stat_sys_headset, contentDescription);//setIconVisibility则根据connected状态设置icon的可见性mIconController.setIconVisibility(mSlotHeadset, true);} else {/*UNISOC: Add for bug 1130932 {@ */AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);if (!audioManager.isWiredHeadsetOn()) {//setIconVisibility则根据connected状态设置icon的可见性mIconController.setIconVisibility(mSlotHeadset, false);}/* @} */}}
2、StatusBarIconControllerImpl
①setIcon
@Override public void setIcon(String slot, int resourceId, CharSequence contentDescription) {//根据slot找到对应的indexint index = getSlotIndex(slot);//用index取得对应的iconStatusBarIconHolder holder = getIcon(index, 0);if (holder == null) {//第一次的时候,icon == null,所以通过new StatusBarIcon创建一个StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),Icon.createWithResource(mContext, resourceId), 0, 0, contentDescription);holder = StatusBarIconHolder.fromIcon(icon);//然后调用此方法setIcon(index, holder);} else {holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);holder.getIcon().contentDescription = contentDescription;handleSet(index, holder);} }@Override public void setIcon(int index, @NonNull StatusBarIconHolder holder) {boolean isNew = getIcon(index, holder.getTag()) == null;super.setIcon(index, holder);if (isNew) {addSystemIcon(index, holder);} else {handleSet(index, holder);} }private void addSystemIcon(int index, StatusBarIconHolder holder) {String slot = getSlotName(index);int viewIndex = getViewIndex(index, holder.getTag());boolean blocked = mIconBlacklist.contains(slot);//onIconAdded-》初始化流程里面StatusBarIconController中添加icon的入口//到这里setIcon就添加完毕了mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder)); }
②setIconVisibility
public void setIconVisibility(String slot, boolean visibility) {int index = getSlotIndex(slot);StatusBarIconHolder holder = getIcon(index, 0);if (holder == null || holder.isVisible() == visibility) {return;}holder.setVisible(visibility);//icon已经创建成功了,icon非空,并且第一次是icon.visible != visibility的//就会顺利的走到handleSet(index, icon)handleSet(index, holder); }private void handleSet(int index, StatusBarIconHolder holder) {int viewIndex = getViewIndex(index, holder.getTag());//初始化流程里面StatusBarIconController中setIcon的地方//到这里setIconVisibility也设置完毕了mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder)); }
五、创建状态栏的窗口
1、StatusBarWindowController
mStatusBarWindowController.attach()
/*** Adds the status bar view to the window manager.*/public void attach() {// 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,mBarHeight,WindowManager.LayoutParams.TYPE_STATUS_BAR,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,PixelFormat.TRANSLUCENT);mLp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;mLp.token = new Binder();mLp.gravity = Gravity.TOP;mLp.setFitInsetsTypes(0 /* types */);mLp.setTitle("StatusBar");mLp.packageName = mContext.getPackageName();mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;//WindowManager中添加view//mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView();//private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;mWindowManager.addView(mStatusBarView, mLp);mLpChanged.copyFrom(mLp);}
2、SuperStatusBarViewFactory
/*** Gets the inflated {@link StatusBarWindowView} from {@link R.layout#super_status_bar}.* Returns a cached instance, if it has already been inflated.*/public StatusBarWindowView getStatusBarWindowView() {if (mStatusBarWindowView != null) {return mStatusBarWindowView;}//由其可知加载的布局来自于super_status_barmStatusBarWindowView =(StatusBarWindowView) mInjectionInflationController.injectable(LayoutInflater.from(mContext)).inflate(R.layout.super_status_bar,/* root= */ null);if (mStatusBarWindowView == null) {throw new IllegalStateException("R.layout.super_status_bar could not be properly inflated");}return mStatusBarWindowView;}
3、super_status_bar.xml
从前面可知这里会用CollapsedStatusBarFragment 替换 status_bar_container(状态栏通知显示区域),完成图标的显示
<!-- This is the status bar window. --> <com.android.systemui.statusbar.phone.StatusBarWindowViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:sysui="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><FrameLayoutandroid:id="@+id/status_bar_container"android:layout_width="match_parent"android:layout_height="wrap_content"android:visibility="gone"/><FrameLayoutandroid:id="@+id/car_top_navigation_bar_container"android:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout></com.android.systemui.statusbar.phone.StatusBarWindowView
六、电池图标刷新流程
1、BatteryMeterView
① 构造方法BatteryMeterView()
public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);BroadcastDispatcher broadcastDispatcher = Dependency.get(BroadcastDispatcher.class);setOrientation(LinearLayout.HORIZONTAL);setGravity(Gravity.CENTER_VERTICAL | Gravity.START);TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,defStyle, 0);final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,context.getColor(R.color.meter_background_color));mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);/*Bug 1296708 add charge animation of batteryView*///添加电池视图的充电动画 truemBatteryAnimation = mContext.getResources().getBoolean(R.bool.config_battery_animation);//将电池等级添加到父布局中if (mBatteryAnimation) {//mBatteryAnimation为truemUnisocDrawable = new BatteryMeterDrawable(context, new Handler(), frameColor, false);}else{mDrawable = new ThemedBatteryDrawable(context, frameColor);}/*@}*/atts.recycle();mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));mShowPercentAvailable = context.getResources().getBoolean(com.android.internal.R.bool.config_battery_percentage_setting_available);addOnAttachStateChangeListener(new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS,Dependency.get(CommandQueue.class)));setupLayoutTransition();mSlotBattery = context.getString(com.android.internal.R.string.status_bar_battery);mBatteryIconView = new ImageView(context);/*Bug 1296708 add charge animation of batteryView*///添加电池视图的充电动画if (mBatteryAnimation) {mBatteryIconView.setImageDrawable(mUnisocDrawable);}else{mBatteryIconView.setImageDrawable(mDrawable);}final MarginLayoutParams mlp = new MarginLayoutParams(getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));mlp.setMargins(0, 0, 0,getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));addView(mBatteryIconView, mlp);updateShowPercent();mDualToneHandler = new DualToneHandler(context);// Init to not dark at all.//设置默认的电池布局的主题色,当状态栏主题发生改变时,电池布局会做相应的更换(亮色和暗色切换)//在 PhoneStatusBarView 中添加了DarkReceiver监听,最终调用到 BatteryMeterView 的onDarkChanged()方法onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);//设置 Settings.System.SHOW_BATTERY_PERCENT 监听mUserTracker = new CurrentUserTracker(broadcastDispatcher) {@Overridepublic void onUserSwitched(int newUserId) {mUser = newUserId;getContext().getContentResolver().unregisterContentObserver(mSettingObserver);getContext().getContentResolver().registerContentObserver(Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver,newUserId);//当用户点击了显示电量百分比开关,则调用 updateShowPercent()方法在电池等级前添加电量百分比updateShowPercent();}};setClipChildren(false);setClipToPadding(false);Dependency.get(ConfigurationController.class).observe(viewAttachLifecycle(this), this);}
②onDarkChanged
//修改百分比的字体颜色和电池等级的画笔颜色和背景颜色@Overridepublic void onDarkChanged(Rect area, float darkIntensity, int tint) {float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0;mNonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity);mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);if (!mUseWallpaperTextColors) {//添加电池充电动画updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,mNonAdaptedSingleToneColor);}}
③updateColors
//在电池等级前添加电量百分比private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {/*Bug 1296708 add charge animation of batteryView*///添加电池充电动画if (mDrawable != null) {mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor);}if (mUnisocDrawable != null) {mUnisocDrawable.setColors(foregroundColor, backgroundColor);}mTextColor = singleToneColor;if (mBatteryPercentView != null) {mBatteryPercentView.setTextColor(singleToneColor);}}
2、PhoneStatusBarView
private DarkReceiver mBattery;@Overridepublic void onFinishInflate() {//mBattery = findViewById(R.id.battery);mCutoutSpace = findViewById(R.id.cutout_space_view);mCenterIconSpace = findViewById(R.id.centered_icon_area);updateResources();}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();// Always have Battery meters in the status bar observe the dark/light modes.//始终在状态栏的电池仪表观察暗/光模式。Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);if (updateOrientationAndCutout()) {updateLayoutForCutout();}}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery);mDisplayCutout = null;}
七、电池状态改变流程
1、BatteryControllerImpl
①init
@Overridepublic void init() {//注册广播registerReceiver();if (!mHasReceivedBattery) {// Get initial state. Relying on Sticky behavior until API for getting info.Intent intent = mContext.registerReceiver(null,new IntentFilter(Intent.ACTION_BATTERY_CHANGED));if (intent != null && !mHasReceivedBattery) {onReceive(mContext, intent);}}updatePowerSave();updateEstimate();}
②registerReceiver
private void registerReceiver() {IntentFilter filter = new IntentFilter();//添加广播的方式接收filter.addAction(Intent.ACTION_BATTERY_CHANGED);filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);/* UNISOC: Bug 1363779 battery icon shows '+' after switching from power saving mode to super power saving @{ */filter.addAction(UnisocPowerManagerUtil.ACTION_POWEREX_SAVE_MODE_CHANGED);/* @} */filter.addAction(ACTION_LEVEL_TEST);mBroadcastDispatcher.registerReceiver(this, filter);}
③onReceive
@Overridepublic void onReceive(final Context context, Intent intent) {final String action = intent.getAction();//监听到ACTION_BATTERY_CHANGEDif (action.equals(Intent.ACTION_BATTERY_CHANGED)) {if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;mHasReceivedBattery = true;mLevel = (int)(100f* intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,BatteryManager.BATTERY_STATUS_UNKNOWN);mCharged = status == BatteryManager.BATTERY_STATUS_FULL;mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;//遍历回调监听,将状态参数发送fireBatteryLevelChanged();}....}
④fireBatteryLevelChanged
protected final ArrayList<BatteryController.BatteryStateChangeCallback>mChangeCallbacks = new ArrayList<>(); protected void fireBatteryLevelChanged() {synchronized (mChangeCallbacks) {final int N = mChangeCallbacks.size();//遍历回调监听,将状态参数发送for (int i = 0; i < N; i++) {mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);}}}
2、BatteryStateChangeCallback
interface BatteryStateChangeCallback {default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {}default void onPowerSaveChanged(boolean isPowerSave) {}default void onReverseChanged(boolean isReverse, int level, String name) {}}
3、BatteryMeterView
BatteryMeterView实现了 BatteryStateChangeCallback,收到改变监听 onBatteryLevelChanged()
//实现了 BatteryStateChangeCallbackpublic class BatteryMeterView extends LinearLayout implementsBatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener {.....@Overridepublic void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {/*Bug 1296708 add charge animation of batteryView*/if (mDrawable != null) {//是否绘制充电中闪电形状图标mDrawable.setCharging(pluggedIn);//根据当前 level/100f 计算百分比绘制pathmDrawable.setBatteryLevel(level);}mCharging = pluggedIn;mLevel = level;updatePercentText();}....}
八、NavigationBar导航栏模块
Ⅰ创建导航栏文件
1、StatusBar
①makeStatusBarView
// ================================================================================// Constructing the view// ================================================================================protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {....//创建导航栏createNavigationBar(result);.....
②createNavigationBar
private final NavigationBarController mNavigationBarController;// TODO(b/117478341): This was left such that CarStatusBar can override this method.// Try to remove this.protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {//mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);...}
2、NavigationBarController
①createNavigationBars
public void createNavigationBars(final boolean includeDefaultDisplay,RegisterStatusBarResult result) {Display[] displays = mDisplayManager.getDisplays();for (Display display : displays) {if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {//createNavigationBar(display, result);}}}
②createNavigationBar
/*** Adds a navigation bar on default display or an external display if the display supports* system decorations.** @param display the display to add navigation bar on.*/@VisibleForTestingvoid createNavigationBar(Display display, RegisterStatusBarResult result) {if (display == null) {return;}final int displayId = display.getDisplayId();final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();try {if (!wms.hasNavigationBar(displayId)) {return;}} catch (RemoteException e) {// Cannot get wms, just return with warning message.Log.w(TAG, "Cannot get WindowManager.");return;}final Context context = isOnDefaultDisplay? mContext: mContext.createDisplayContext(display);//最终是通过NavigationBarFragment的create方法进行创建NavigationBarFragment.create(context, (tag, fragment) -> {NavigationBarFragment navBar = (NavigationBarFragment) fragment;
3、NavigationBarFragment
①create
代码做了两件事:
1.创建navigationBarView 并且把navigationBarView添加到windowManager中。
2.创建NavigationBarFragment 替换navigation_bar_window的布局文件,改成navigation_bar
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" + context.getDisplayId());lp.accessibilityTitle = context.getString(R.string.nav_bar);lp.windowAnimations = 0;lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;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;//创建NavigationBarFragment 替换navigation_bar_window的布局文件,改成navigation_barfinal NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView).create(NavigationBarFragment.class);navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {@Overridepublic void onViewAttachedToWindow(View v) {final FragmentHostManager fragmentHost = FragmentHostManager.get(v);//navigation_bar_frame是navigation_bar_window中NavigationBarFrame的IDfragmentHost.getFragmentManager().beginTransaction().replace(R.id.navigation_bar_frame, fragment, TAG).commit();fragmentHost.addTagListener(TAG, listener);}@Overridepublic void onViewDetachedFromWindow(View v) {FragmentHostManager.removeAndDestroy(v);navigationBarView.removeOnAttachStateChangeListener(this);}});context.getSystemService(WindowManager.class).addView(navigationBarView, lp);return navigationBarView;}
③onCreateView
@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,Bundle savedInstanceState) {//return inflater.inflate(R.layout.navigation_bar, container, false);}
4、navigation_bar_window.xml
<com.android.systemui.statusbar.phone.NavigationBarFramexmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:id="@+id/navigation_bar_frame"android:theme="@style/Theme.SystemUI"android:layout_height="match_parent"android:layout_width="match_parent"></com.android.systemui.statusbar.phone.NavigationBarFrame>
5、navigation_bar.xml
<com.android.systemui.statusbar.phone.NavigationBarViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:layout_height="match_parent"android:layout_width="match_parent"android:background="@drawable/system_bar_background"><com.android.systemui.CornerHandleViewandroid:id="@+id/assist_hint_left"android:layout_width="40dp"android:layout_height="40dp"android:layout_gravity="left|bottom"android:rotation="270"android:visibility="gone"/><com.android.systemui.CornerHandleViewandroid:id="@+id/assist_hint_right"android:layout_width="40dp"android:layout_height="40dp"android:layout_gravity="right|bottom"android:rotation="180"android:visibility="gone"/><com.android.systemui.statusbar.phone.NavigationBarInflaterViewandroid:id="@+id/navigation_inflater"android:layout_width="match_parent"android:layout_height="match_parent" /></com.android.systemui.statusbar.phone.NavigationBarView>
Ⅱ加载布局文件
6、NavigationBarInflaterView
①NavigationBarInflaterView(Context context, AttributeSet attrs)
public NavigationBarInflaterView(Context context, AttributeSet attrs) {super(context, attrs);createInflaters();mOverviewProxyService = Dependency.get(OverviewProxyService.class);mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);/* UNISOC: add for bug 1071183,1134237 @{ */mSupportDynamicBar = NavigationBarView.isSupportDynamicNavBar(context, mNavBarMode);/* }@ */}
②createInflaters
@VisibleForTestingvoid createInflaters() {mLayoutInflater = LayoutInflater.from(mContext);Configuration landscape = new Configuration();landscape.setTo(mContext.getResources().getConfiguration());landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));}
③onFinishInflate
@Overrideprotected void onFinishInflate() {super.onFinishInflate();//添加水平横屏布局inflateChildren();//清空布局clearViews();inflateLayout(getDefaultLayout());}
④inflateChildren
private void inflateChildren() {removeAllViews();//水平布局mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout,this /* root */, false /* attachToRoot */);addView(mHorizontal);//垂直布局mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical,this /* root */, false /* attachToRoot */);addView(mVertical);updatealternativeorder();}
⑤clearViews
private void clearViews() {if (mButtonDispatchers != null) {for (int i = 0; i < mButtonDispatchers.size(); i++) {mButtonDispatchers.valueAt(i).clear();}}clearAllChildren(mHorizontal.findViewById(R.id.nav_buttons));clearAllChildren(mVertical.findViewById(R.id.nav_buttons));}private void clearAllChildren(ViewGroup group) {for (int i = 0; i < group.getChildCount(); i++) {((ViewGroup) group.getChildAt(i)).removeAllViews();}}
⑥inflateLayout
protected void inflateLayout(String newLayout) {mCurrentLayout = newLayout;if (newLayout == null) {newLayout = getDefaultLayout();}String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);if (sets.length != 3) {Log.d(TAG, "Invalid layout.");newLayout = getDefaultLayout();sets = newLayout.split(GRAVITY_SEPARATOR, 3);}String[] start = sets[0].split(BUTTON_SEPARATOR);String[] center = sets[1].split(BUTTON_SEPARATOR);String[] end = sets[2].split(BUTTON_SEPARATOR);// Inflate these in start to end order or accessibility traversal will be messed up.inflateButtons(start, mHorizontal.findViewById(R.id.ends_group),false /* landscape */, true /* start */);inflateButtons(start, mVertical.findViewById(R.id.ends_group),true /* landscape */, true /* start */);inflateButtons(center, mHorizontal.findViewById(R.id.center_group),false /* landscape */, false /* start */);inflateButtons(center, mVertical.findViewById(R.id.center_group),true /* landscape */, false /* start */);addGravitySpacer(mHorizontal.findViewById(R.id.ends_group));addGravitySpacer(mVertical.findViewById(R.id.ends_group));inflateButtons(end, mHorizontal.findViewById(R.id.ends_group),false /* landscape */, false /* start */);inflateButtons(end, mVertical.findViewById(R.id.ends_group),true /* landscape */, false /* start */);updateButtonDispatchersCurrentView();}
⑦inflateButton
private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,boolean start) {for (int i = 0; i < buttons.length; i++) {inflateButton(buttons[i], parent, landscape, start);}}@Nullableprotected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,boolean start) {LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;View v = createView(buttonSpec, parent, inflater);if (v == null) return null;v = applySize(v, buttonSpec, landscape, start);parent.addView(v);addToDispatchers(v);View lastView = landscape ? mLastLandscape : mLastPortrait;View accessibilityView = v;if (v instanceof ReverseRelativeLayout) {accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);}if (lastView != null) {accessibilityView.setAccessibilityTraversalAfter(lastView.getId());}if (landscape) {mLastLandscape = accessibilityView;} else {mLastPortrait = accessibilityView;}return v;}
⑧getDefaultLayout
//导航栏显示哪些控件是由getDefaultLayout来决定protected String getDefaultLayout() {/* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */if (mSupportDynamicBar) {return readLNavigationLayoutSettings();}/* @} */final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)? R.string.config_navBarLayoutHandle: mOverviewProxyService.shouldShowSwipeUpUI()? R.string.config_navBarLayoutQuickstep: R.string.config_navBarLayout;return getContext().getString(defaultResource);}
⑨createView
private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {View v = null;String button = extractButton(buttonSpec);if (LEFT.equals(button)) {button = extractButton(NAVSPACE);} else if (RIGHT.equals(button)) {button = extractButton(MENU_IME_ROTATE);}if (HOME.equals(button)) {v = inflater.inflate(R.layout.home, parent, false);} else if (BACK.equals(button)) {v = inflater.inflate(R.layout.back, parent, false);} else if (RECENT.equals(button)) {v = inflater.inflate(R.layout.recent_apps, parent, false);} else if (MENU_IME_ROTATE.equals(button)) {v = inflater.inflate(R.layout.menu_ime, parent, false);} else if (NAVSPACE.equals(button)) {v = inflater.inflate(R.layout.nav_key_space, parent, false);} else if (CLIPBOARD.equals(button)) {v = inflater.inflate(R.layout.clipboard, parent, false);} else if (CONTEXTUAL.equals(button)) {v = inflater.inflate(R.layout.contextual, parent, false);} else if (HOME_HANDLE.equals(button)) {v = inflater.inflate(R.layout.home_handle, parent, false);} else if (IME_SWITCHER.equals(button)) {v = inflater.inflate(R.layout.ime_switcher, parent, false);} else if (button.startsWith(KEY)) {String uri = extractImage(button);int code = extractKeycode(button);v = inflater.inflate(R.layout.custom_key, parent, false);((KeyButtonView) v).setCode(code);if (uri != null) {if (uri.contains(":")) {((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));} else if (uri.contains("/")) {int index = uri.indexOf('/');String pkg = uri.substring(0, index);int id = Integer.parseInt(uri.substring(index + 1));((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));}}}/* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */else if (HIDE.equals(button)) {v = inflater.inflate(R.layout.hide, parent, false);} else if (PULL.equals(button)) {v = inflater.inflate(R.layout.pull, parent, false);/*UNISOC: Add for bug 902309 1146896 @{ */} else if (SPACE_PLACE.equals(button)) {v = inflater.inflate(R.layout.space, parent, false);/* }@ */} else {return null;}/* @} */return v;}
7、home.xml、back.xml、recent_apps.xml
<com.android.systemui.statusbar.policy.KeyButtonViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:id="@+id/home"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:layout_weight="0"systemui:keyCode="3"android:scaleType="center"android:contentDescription="@string/accessibility_home"android:paddingStart="@dimen/navigation_key_padding"android:paddingEnd="@dimen/navigation_key_padding"/><com.android.systemui.statusbar.policy.KeyButtonViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:id="@+id/back"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:layout_weight="0"systemui:keyCode="4"android:scaleType="center"android:contentDescription="@string/accessibility_back"android:paddingStart="@dimen/navigation_key_padding"android:paddingEnd="@dimen/navigation_key_padding"/><com.android.systemui.statusbar.policy.KeyButtonViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:id="@+id/recent_apps"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:layout_weight="0"android:scaleType="center"android:contentDescription="@string/accessibility_recent"android:paddingStart="@dimen/navigation_key_padding"android:paddingEnd="@dimen/navigation_key_padding"/>
8、config
<!-- Nav bar button default ordering/layout --><string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string><string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string><string name="config_navBarLayoutHandle" translatable="false">back[40AC];home_handle;ime_switcher[40AC]</string>
第一、导航栏显示哪些控件是由getDefaultLayout来决定。
<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
一般情况下我们是home,recent,back这三个键,如果你需要加其他的就在这个配置文件夹。同时在createView添加对应的布局文件。
第二、createView方法创建对应的布局文件,并且添加到导航栏中。
那么我们现在布局文件都添加完成了,但是你会发现在NavigationBarInflaterView没有对资源文件添加的代码已经控件点击触摸事件处理逻辑。那么这两部分代码在哪里呢?
答案是:
1.NavigationBarView 完成资源文件添加。
2.NavigationBarFragment 添加点击事件和触摸事件的处理逻辑。
Ⅲ资源文件添加
1、NavigationBarView
①NavigationBarView(Context context, AttributeSet attrs)
public NavigationBarView(Context context, AttributeSet attrs) {super(context, attrs);mIsVertical = false;mLongClickableAccessibilityButton = false;mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);//UNISOC: Add for bug 1242615mOldNavBarMode = mNavBarMode;/* UNISCO: Bug 1072090,1116092 new feature of dynamic navigationbar @{*/mSupportDynamicBar = isSupportDynamicNavBar(mContext, mNavBarMode);mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);/* }@ *//* UNISOC: Modify for bug963304 {@ */mStatusBarManager = (StatusBarManager) mContext.getSystemService(android.app.Service.STATUS_BAR_SERVICE);/* @} */boolean isGesturalMode = isGesturalMode(mNavBarMode);mSysUiFlagContainer = Dependency.get(SysUiState.class);mPluginManager = Dependency.get(PluginManager.class);// Set up the context group of buttonsmContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,R.drawable.ic_ime_switcher_default);final RotationContextButton rotateSuggestionButton = new RotationContextButton(R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);final ContextualButton accessibilityButton =new ContextualButton(R.id.accessibility_button,R.drawable.ic_sysbar_accessibility_button);mContextualButtonGroup.addButton(imeSwitcherButton);if (!isGesturalMode) {mContextualButtonGroup.addButton(rotateSuggestionButton);}mContextualButtonGroup.addButton(accessibilityButton);mOverviewProxyService = Dependency.get(OverviewProxyService.class);mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);mFloatingRotationButton = new FloatingRotationButton(context);mRotationButtonController = new RotationButtonController(context,R.style.RotateButtonCCWStart90,isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);mConfiguration = new Configuration();mTmpLastConfiguration = new Configuration();mConfiguration.updateFrom(context.getResources().getConfiguration());mScreenPinningNotify = new ScreenPinningNotify(mContext);mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);mDeadZone = new DeadZone(this);/* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */if(mSupportDynamicBar){mStatusBar = Dependency.get(StatusBar.class);mButtonDispatchers.put(R.id.hide, new ButtonDispatcher(R.id.hide));mButtonDispatchers.put(R.id.pull, new ButtonDispatcher(R.id.pull));}mNavColorSampleMargin = getResources().getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);//updateStates更新状态mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService,mSysUiFlagContainer, mPluginManager, this::updateStates);mRegionSamplingHelper = new RegionSamplingHelper(this,new RegionSamplingHelper.SamplingCallback() {@Overridepublic void onRegionDarknessChanged(boolean isRegionDark) {getLightTransitionsController().setIconsDark(!isRegionDark ,true /* animate */);}@Overridepublic Rect getSampledRegion(View sampledView) {if (mOrientedHandleSamplingRegion != null) {return mOrientedHandleSamplingRegion;}updateSamplingRect();return mSamplingBounds;}@Overridepublic boolean isSamplingEnabled() {return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);}});}
②updateStates
public void updateStates() {final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI();if (mNavigationInflaterView != null) {// Reinflate the navbar if needed, no-op unless the swipe up state changesmNavigationInflaterView.onLikelyDefaultLayoutChange();}updateSlippery();//初始化加载资源,主要是图片reloadNavIcons();updateNavButtonIcons();setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(!showSwipeUpUI);getHomeButton().setAccessibilityDelegate(showSwipeUpUI ? mQuickStepAccessibilityDelegate : null);}
③reloadNavIcons
//初始化加载资源,主要是图片private void reloadNavIcons() {updateIcons(Configuration.EMPTY);}
Ⅳ添加点击事件和触摸事件的处理逻辑。
1、NavigationBarFragment
①onViewCreated
@Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);mNavigationBarView = (NavigationBarView) view;final Display display = view.getDisplay();// It may not have display when running unit test.if (display != null) {mDisplayId = display.getDisplayId();mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;}mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());mNavigationBarView.setDisabledFlags(mDisabledFlags1);mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);mNavigationBarView.setOnTouchListener(this::onNavigationTouch);if (savedInstanceState != null) {mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);}mNavigationBarView.setNavigationIconHints(mNavigationIconHints);mNavigationBarView.setWindowVisible(isNavBarWindowVisible());添加home,recent触摸事件回调prepareNavigationBarView();checkNavBarModes();IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);filter.addAction(Intent.ACTION_SCREEN_ON);filter.addAction(Intent.ACTION_USER_SWITCHED);//UNISOC: Add for bug 1274603filter.addAction(Intent.ACTION_USER_PRESENT);filter.addAction(PowerManagerEx.ACTION_POWEREX_SAVE_MODE_CHANGED);mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,Handler.getMain(), UserHandle.ALL);notifyNavigationBarScreenOn();mOverviewProxyService.addCallback(mOverviewProxyListener);updateSystemUiStateFlags(-1);......}
②prepareNavigationBarView
setOnClickListener,setOnTouchListener,setLongClickable,setOnLongClickListener就是给对应的控件添加控制代码
private void prepareNavigationBarView() {mNavigationBarView.reorient();ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();recentsButton.setOnClickListener(this::onRecentsClick);recentsButton.setOnTouchListener(this::onRecentsTouch);recentsButton.setLongClickable(true);recentsButton.setOnLongClickListener(this::onLongPressBackRecents);ButtonDispatcher backButton = mNavigationBarView.getBackButton();backButton.setLongClickable(true);ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();homeButton.setOnTouchListener(this::onHomeTouch);homeButton.setOnLongClickListener(this::onHomeLongClick);ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();accessibilityButton.setOnClickListener(this::onAccessibilityClick);accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);updateAccessibilityServicesState(mAccessibilityManager);updateScreenPinningGestures();}
③onHomeTouch
private boolean onHomeTouch(View v, MotionEvent event) {if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {return true;}// If an incoming call is ringing, HOME is totally disabled.// (The user is already on the InCallUI at this point,// and his ONLY options are to answer or reject the call.)switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mHomeBlockedThisTouch = false;TelecomManager telecomManager =getContext().getSystemService(TelecomManager.class);if (telecomManager != null && telecomManager.isRinging()) {if (mStatusBarLazy.get().isKeyguardShowing()) {Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +"No heads up");mHomeBlockedThisTouch = true;return true;}}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:mStatusBarLazy.get().awakenDreams();break;}return false;}
④onHomeLongClick
@VisibleForTestingboolean onHomeLongClick(View v) {if (!mNavigationBarView.isRecentsButtonVisible()&& ActivityManagerWrapper.getInstance().isScreenPinningActive()) {return onLongPressBackHome(v);}if (shouldDisableNavbarGestures()) {return false;}mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);/* UNISOC: Bug 1074234, 970184, Super power feature @{ */if (UnisocPowerManagerUtil.isSuperPower()) {Log.d(TAG, "onHomeLongClick SUPPORT_SUPER_POWER_SAVE ignore!");return false;}/* @} */mUiEventLogger.log(NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS);Bundle args = new Bundle();args.putInt(AssistManager.INVOCATION_TYPE_KEY, AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS);mAssistManager.startAssist(args);mStatusBarLazy.get().awakenDreams();if (mNavigationBarView != null) {mNavigationBarView.abortCurrentGesture();}return true;}
九、Recents模块
packages/apps/SystemUI/src/com/android/systemui/recents/
1、Recents
public class Recents extends SystemUI implements CommandQueue.Callbacks {private final RecentsImplementation mImpl;private final CommandQueue mCommandQueue;public Recents(Context context, RecentsImplementation impl, CommandQueue commandQueue) {super(context);mImpl = impl;mCommandQueue = commandQueue;}//由前面SystemUI的启动可知,调用的都为子类的start方法//在start方法添加了回调和调用了RecentsImplementation的onStart方法,下面跟进RecentsImplementation@Overridepublic void start() {mCommandQueue.addCallback(this);mImpl.onStart(mContext);}... }
2、RecentsImplementation
public interface RecentsImplementation {//可以看到该接口中方法皆为default修饰的方法,但均未写函数体,具体实现由子类实现,于是跟进OverviewProxyRecentsImpl类default void onStart(Context context) {}default void onBootCompleted() {}default void onAppTransitionFinished() {}default void onConfigurationChanged(Configuration newConfig) {}default void preloadRecentApps() {}default void cancelPreloadRecentApps() {}default void showRecentApps(boolean triggeredFromAltTab) {}default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {}default void toggleRecentApps() {}default void growRecents() {}default boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,int metricsDockAction) {return false;}default void dump(PrintWriter pw) {} }
3、OverviewProxyRecentsImpl
/*** An implementation of the Recents interface which proxies to the OverviewProxyService.*/ @Singleton public class OverviewProxyRecentsImpl implements RecentsImplementation {private final static String TAG = "OverviewProxyRecentsImpl";@Nullableprivate final Lazy<StatusBar> mStatusBarLazy;private final Optional<Divider> mDividerOptional;private Context mContext;private Handler mHandler;private TrustManager mTrustManager;private OverviewProxyService mOverviewProxyService;@SuppressWarnings("OptionalUsedAsFieldOrParameterType")@Injectpublic OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy,Optional<Divider> dividerOptional) {mStatusBarLazy = statusBarLazy.orElse(null);mDividerOptional = dividerOptional;}//可见之前调用的onStart()方法具体是调用的该子类的重写方法@Overridepublic void onStart(Context context) {mContext = context;mHandler = new Handler();mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE);mOverviewProxyService = Dependency.get(OverviewProxyService.class);}@Overridepublic void toggleRecentApps() {// If connected to launcher service, let it handle the toggle logicIOverviewProxy overviewProxy = mOverviewProxyService.getProxy();if (overviewProxy != null) {final Runnable toggleRecents = () -> {try {if (mOverviewProxyService.getProxy() != null) {//可以看到显示最近的app的方法都是通过得到OverviewProxyService的代理,之后对其操作,//接着跟进OverviewProxyService类查看overviewProxy的由来mOverviewProxyService.getProxy().onOverviewToggle();mOverviewProxyService.notifyToggleRecentApps();}} catch (RemoteException e) {Log.e(TAG, "Cannot send toggle recents through proxy service.", e);}};// Preload only if device for current user is unlockedif (mStatusBarLazy != null && mStatusBarLazy.get().isKeyguardShowing()) {mStatusBarLazy.get().executeRunnableDismissingKeyguard(() -> {// Flush trustmanager before checking device locked per usermTrustManager.reportKeyguardShowingChanged();mHandler.post(toggleRecents);}, null, true /* dismissShade */, false /* afterKeyguardGone */,true /* deferred */);} else {toggleRecents.run();}return;} else {// Do nothing}} }
4、OverviewProxyService
①getProxy
private IOverviewProxy mOverviewProxy;//可以看到getProxy()方法返回的是一个mOverviewProxy:IOverviewProxy对象引用,接下来查看其具体指向哪个对象public IOverviewProxy getProxy() {return mOverviewProxy;}
②ServiceConnection
//通过mOverviewServiceConnection应该可以发现,应该是bindService中的一个参数。private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {if (SysUiState.DEBUG) {Log.d(TAG_OPS, "Overview proxy service connected");}mConnectionBackoffAttempts = 0;mHandler.removeCallbacks(mDeferredConnectionCallback);try {service.linkToDeath(mOverviewServiceDeathRcpt, 0);} catch (RemoteException e) {// Failed to link to death (process may have died between binding and connecting),// just unbind the service for now and retry againLog.e(TAG_OPS, "Lost connection to launcher service", e);disconnectFromLauncherService();retryConnectionWithBackoff();return;}mCurrentBoundedUserId = getCurrentUserId();//mOverviewProxy指向了IOverviewProxy的一个远程代理mOverviewProxy = IOverviewProxy.Stub.asInterface(service);Bundle params = new Bundle();params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);try {mOverviewProxy.onInitialize(params);} catch (RemoteException e) {mCurrentBoundedUserId = -1;Log.e(TAG_OPS, "Failed to call onInitialize()", e);}dispatchNavButtonBounds();......}
③internalConnectToCurrentUser
private void internalConnectToCurrentUser() {disconnectFromLauncherService();// If user has not setup yet or already connected, do not try to connectif (!isEnabled()) {Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled());return;}mHandler.removeCallbacks(mConnectionRunnable);//ACTION_QUICKSTEP,这个action就是Launcher中的Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP);if (mRecentsComponentName != null) {launcherServiceIntent.setPackage(mRecentsComponentName.getPackageName());}try {//传入的intent为launcherServiceIntent,其参数为ACTION_QUICKSTEP,查看定义这个action就是Launcher中的mBound = mContext.bindServiceAsUser(launcherServiceIntent,mOverviewServiceConnection,Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,UserHandle.of(getCurrentUserId()));} catch (SecurityException e) {Log.e(TAG_OPS, "Unable to bind because of security error", e);} catch (IllegalArgumentException e) {Log.e(TAG_OPS, "Unable to bind because of illegal argument error", e);}if (mBound) {// Ensure that connection has been established even if it thinks it is boundmHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS);} else {// Retry after exponential backoff timeoutretryConnectionWithBackoff();}}
5、AndroidManifest.xml
代码位于packages/apps/Launcher3/quickstep/AndroidManifest.xml
<service//TouchInteractionServiceandroid:name="com.android.quickstep.TouchInteractionService"android:permission="android.permission.STATUS_BAR_SERVICE"android:directBootAware="true" ><intent-filter><action android:name="android.intent.action.QUICKSTEP_SERVICE" /></intent-filter></service>
6、TouchInteractionService
private OverviewCommandHelper mOverviewCommandHelper;private final IBinder mMyBinder = new IOverviewProxy.Stub() {@BinderThreadpublic void onInitialize(Bundle bundle) {ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));MAIN_EXECUTOR.execute(() -> {SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);TouchInteractionService.this.initInputMonitor();preloadOverview(true /* fromInit */);});sIsInitialized = true;}@BinderThread@Override//也就是说,上面OverviewProxyRecentsImpl调用的mOverviewProxyService.getProxy().onOverviewToggle()//其实是调用TouchInteractionService中的mMyBinder的实现,mMyBinder就是IOverviewProxy的一个远程代理。public void onOverviewToggle() {TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");mOverviewCommandHelper.onOverviewToggle();}....}
7、OverviewCommandHelper
①onOverviewToggle
//也就是说,上面OverviewProxyRecentsImpl调用的mOverviewProxyService.getProxy().onOverviewToggle()@BinderThreadpublic void onOverviewToggle() {// If currently screen pinning, do not enter overviewif (mDeviceState.isScreenPinningActive()) {return;}ActivityManagerWrapper.getInstance().closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);//可以看到这里主要启动了RecentsActivityCommand线程MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());}public RecentsActivityCommand() {mActivityInterface = mOverviewComponentObserver.getActivityInterface();mCreateTime = SystemClock.elapsedRealtime();mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,RecentsModel.getRunningTaskId(), mDeviceState);// Preload the planmRecentsModel.getTasks(null);}
②run()
最终实现了一个从点击switch到Launcher的RecentsActivity启动的过程
@Overridepublic void run() {long elapsedTime = mCreateTime - mLastToggleTime;mLastToggleTime = mCreateTime;if (handleCommand(elapsedTime)) {// Command already handled.return;}if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {// If successfully switched, then returnreturn;}// Otherwise, start overview.mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),new RemoteAnimationProvider() {@Overridepublic AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,RemoteAnimationTargetCompat[] wallpaperTargets) {return RecentsActivityCommand.this.createWindowAnimation(appTargets,wallpaperTargets);}}, mContext, MAIN_EXECUTOR.getHandler(),mAnimationProvider.getRecentsLaunchDuration());}
十、VolumeUI模块
这个模块使用MVP架构完成设计的
通过 SystemUI之StatusBar创建 可知,VolumeUI 的入口为 VolumeUI#start()
Ⅰ MVP架构绑定流程
1、VolumeUI
①start()
@Overridepublic void start() {boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);boolean enableSafetyWarning =mContext.getResources().getBoolean(R.bool.enable_safety_warning);mEnabled = enableVolumeUi || enableSafetyWarning;if (!mEnabled) return;//mVolumeComponent从名字可以看出,它代表 VolumeUI 组件,通过它可以创建整个MVP。mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);//register启动VolumeUI的功能setDefaultVolumeController();}
②VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent)
//VolumeUI 启动的时候会创建一个 VolumeDialogComponent 对象@Injectpublic VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {super(context);mVolumeComponent = volumeDialogComponent;}
③setDefaultVolumeController
private void setDefaultVolumeController() {DndTile.setVisible(mContext, true);if (D.BUG) Log.d(TAG, "Registering default volume controller");//VolumeDialogComponent 对象创建完成后,就会调用它的register()方法启动 VolumeUI 功能。//register启动VolumeUI的功能//它其实就是关联 Presenter 层和 Model 层。mVolumeComponent.register();}
④VolumeDialogComponent的register方法
private final VolumeDialogControllerImpl mController;@Overridepublic void register() {mController.register();DndTile.setCombinedIcon(mContext, true);}
⑤VolumeDialogControllerImpl的register方法
public void register() {setVolumeController();setVolumePolicy(mVolumePolicy);showDndTile(mShowDndTile);try {mMediaSessions.init();} catch (SecurityException e) {Log.w(TAG, "No access to media sessions", e);}}
2、VolumeDialogComponent
①VolumeDialogComponent 的构造函数
//接口,实现类为VolumeDialogImplprivate VolumeDialog mDialog;@Injectpublic VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,VolumeDialogControllerImpl volumeDialogController) {mContext = context;mKeyguardViewMediator = keyguardViewMediator;mController = volumeDialogController;mController.setUserActivityListener(this);// Allow plugins to reference the VolumeDialogController.Dependency.get(PluginDependencyProvider.class).allowPluginDependency(VolumeDialogController.class);Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class).withPlugin(VolumeDialog.class)//VolumeDialogComponent 通过 createDefault() 创建 VolumeDialogImpl 对象,它代表 View 层.withDefault(this::createDefault).withCallback(dialog -> {if (mDialog != null) {mDialog.destroy();}mDialog = dialog;//然后通过init() 进行了初始化。mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);}).build();applyConfiguration();Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,VOLUME_SILENT_DO_NOT_DISTURB);}
②createDefault()
protected VolumeDialog createDefault() {VolumeDialogImpl impl = new VolumeDialogImpl(mContext);impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);impl.setAutomute(true);impl.setSilentMode(false);return impl;}
2、VolumeDialogImpl---View层
①init(int windowType, Callback callback)
public void init(int windowType, Callback callback) {initDialog();mAccessibility.init();//向 VolumeDialogControllerImpl (Presenter层) 注册一个回调//也就是 View 层与 Presenter 层建立关联,从而可以通过 Presenter 层控制 View 层。mController.addCallback(mControllerCallbackH, mHandler);mController.getState();Dependency.get(ConfigurationController.class).addCallback(this);}
3、VolumeDialogControllerImpl---P层
①register
//进行AudioManager的关联,也就是presenter层和model层的关联public void register() {//进行AudioManager的关联setVolumeController();setVolumePolicy(mVolumePolicy);showDndTile(mShowDndTile);try {mMediaSessions.init();} catch (SecurityException e) {Log.w(TAG, "No access to media sessions", e);}}
②setVolumeController()
private AudioManager mAudio;protected final VC mVolumeController = new VC();protected void setVolumeController() {try {mAudio.setVolumeController(mVolumeController);} catch (SecurityException e) {Log.w(TAG, "Unable to set the volume controller", e);return;}}
Ⅱ 按下 Power 键后,VolumeUI 是如何显示UI的
由于 VolumeDialogControllerImpl 向AudioManager注册了回调,当按下音量键调整了音量后,VolumeDialogControllerImpl 就会收到回调
1、VC(VolumeDialogControllerImpl内部类)
private final W mWorker; private final class VC extends IVolumeController.Stub {private final String TAG = VolumeDialogControllerImpl.TAG + ".VC";......@Overridepublic void volumeChanged(int streamType, int flags) throws RemoteException {if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)+ " " + Util.audioManagerFlagsToString(flags));if (mDestroyed) return;//mWorker为继承于Handler的内部final类,根据收到的消息不同处理mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();}..........}
2、W(VolumeDialogControllerImpl内部类)
①handleMessage
private final class W extends Handler {private static final int VOLUME_CHANGED = 1;private static final int DISMISS_REQUESTED = 2;.....W(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {//当消息为VOLUME_CHANGED时,调用onVolumeChangedW方法case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break;case GET_STATE: onGetStateW(); break;case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break;case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break;case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break;case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break;case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break;case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break;case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break;case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break;case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;case USER_ACTIVITY: onUserActivityW(); break;case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;case GET_CAPTIONS_COMPONENT_STATE:onGetCaptionsComponentStateW((Boolean) msg.obj); break;case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);}}}
②onVolumeChangedW
boolean onVolumeChangedW(int stream, int flags) {//根据 flags 决定要执行哪个回调,如果要显示UI,就会回调 onShowRequested()final boolean showUI = shouldShowUI(flags);final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;boolean changed = false;if (showUI) {changed |= updateActiveStreamW(stream);}int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream);changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);if (changed) {mCallbacks.onStateChanged(mState);}//这个回调当然是由 View 层实现的,也就是在VolumeDialogImpl中调用if (showUI) {mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);}if (showVibrateHint) {mCallbacks.onShowVibrateHint();}if (showSilentHint) {mCallbacks.onShowSilentHint();}if (changed && fromKey) {Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume);}return changed;}
3、VolumeDialogImpl
①onShowRequested
public void init(int windowType, Callback callback) {initDialog();mAccessibility.init();//初始化的时候添加回调addCallbackmController.addCallback(mControllerCallbackH, mHandler);mController.getState();Dependency.get(ConfigurationController.class).addCallback(this);}//mControllerCallbackHprivate final VolumeDialogController.Callbacks mControllerCallbackH= new VolumeDialogController.Callbacks() {@Overridepublic void onShowRequested(int reason) {//showH(reason);}}
②showH
private void showH(int reason) {if (D.BUG) Log.d(TAG, "showH r=" + Events.SHOW_REASONS[reason]);mHandler.removeMessages(H.SHOW);mHandler.removeMessages(H.DISMISS);rescheduleTimeoutH();/* UNISOC: Modify for bug1347675,1384445 @{ */Configuration config = mContext.getResources().getConfiguration();boolean orientationPortrait = config.orientation == ORIENTATION_PORTRAIT;if ((mConfigChanged || (mOrientationPortrait != orientationPortrait)) && !mDialog.isShowing()) {initDialog(); // resets mShowing to falsemConfigurableTexts.update();mConfigChanged = false;mOrientationPortrait = orientationPortrait;}/* @} */initSettingsH();mShowing = true;mIsAnimatingDismiss = false;mDialog.show();Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());mController.notifyVisible(true);mController.getCaptionsComponentState(false);checkODICaptionsTooltip(false);}
ⅢAudioService对音量键处理流程
1、PhoneWindowManager
①dispatchDirectAudioEvent
// pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP,// KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTEprivate void dispatchDirectAudioEvent(KeyEvent event) {// When System Audio Mode is off, volume keys received by AVR can be either consumed by AVR// or forwarded to the TV. It's up to Amplifier manufacturer’s implementation.HdmiControlManager hdmiControlManager = getHdmiControlManager();if (null != hdmiControlManager&& !hdmiControlManager.getSystemAudioMode()&& shouldCecAudioDeviceForwardVolumeKeysSystemAudioModeOff()) {HdmiAudioSystemClient audioSystemClient = hdmiControlManager.getAudioSystemClient();if (audioSystemClient != null) {audioSystemClient.sendKeyEvent(event.getKeyCode(), event.getAction() == KeyEvent.ACTION_DOWN);return;}}try {//这里通过AIDL获取IAudioService的实例getAudioService().handleVolumeKey(event, mUseTvRouting,mContext.getOpPackageName(), TAG);} catch (Exception e) {Log.e(TAG, "Error dispatching volume key in handleVolumeKey for event:"+ event, e);}}
②getAudioService()
import android.media.IAudioService;static IAudioService getAudioService() {IAudioService audioService = IAudioService.Stub.asInterface(ServiceManager.checkService(Context.AUDIO_SERVICE));if (audioService == null) {Log.w(TAG, "Unable to find IAudioService interface.");}return audioService;}
这里是直接执行了音频键的操作,通过Binder获取到了AudioService的实例,去调用了handleVolumeKey方法,参数含义如下:
按键类型 | Audio Service操作类型 | 含义 |
KEYCODE_VOLUME_UP | AudioManager.ADJUST_RAISE | 音量加 |
KEYCODE_VOLUME_DOWN | AudioManager.ADJUST_LOWER | 音量减 |
KEYCODE_VOLUME_MUTE | AudioManager.ADJUST_TOGGLE_MUTE | 改变静音状态 |
2、AudioService
①handleVolumeKey
//AudioService继承了IAudioService public class AudioService extends IAudioService.Stubimplements AccessibilityManager.TouchExplorationStateChangeListener,AccessibilityManager.AccessibilityServicesStateChangeListener {// pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP,// KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTEpublic void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv,@NonNull String callingPackage, @NonNull String caller) {int keyEventMode = VOL_ADJUST_NORMAL;if (isOnTv) {if (event.getAction() == KeyEvent.ACTION_DOWN) {keyEventMode = VOL_ADJUST_START;} else { // may catch more than ACTION_UP, but will end vol adjustement// the vol key is either released (ACTION_UP), or multiple keys are pressed// (ACTION_MULTIPLE) and we don't know what to do for volume control on CEC, end// the repeated volume adjustementkeyEventMode = VOL_ADJUST_END;}} else if (event.getAction() != KeyEvent.ACTION_DOWN) {return;}int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND| AudioManager.FLAG_FROM_KEY;switch (event.getKeyCode()) {case KeyEvent.KEYCODE_VOLUME_UP://在按键的处理过程中,并没有将相应的code传递给AudioService,//而是使用了相关的定义,将KEYCODE_VOLUME_UP等操作转化为了ADJUST_RAISE等。//而flag存储了一些对音量的要求或者信息吧,这个也很重要。adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,Binder.getCallingUid(), true, keyEventMode);break;case KeyEvent.KEYCODE_VOLUME_DOWN:adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,Binder.getCallingUid(), true, keyEventMode);break;case KeyEvent.KEYCODE_VOLUME_MUTE:if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE,AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,Binder.getCallingUid(), true, VOL_ADJUST_NORMAL);}break;default:Log.e(TAG, "Invalid key code " + event.getKeyCode() + " sent by " + callingPackage);return; // not needed but added if code gets added below this switch statement}}}
②adjustSuggestedStreamVolume
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,int keyEventMode) {if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType+ ", flags=" + flags + ", caller=" + caller+ ", volControlStream=" + mVolumeControlStream+ ", userSelect=" + mUserSelectedVolumeControlStream);if (direction != AudioManager.ADJUST_SAME) {sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage).append("/").append(caller).append(" uid:").append(uid).toString()));}boolean hasExternalVolumeController = notifyExternalVolumeController(direction);new MediaMetrics.Item(mMetricsId + "adjustSuggestedStreamVolume").setUid(Binder.getCallingUid()).set(MediaMetrics.Property.CALLING_PACKAGE, callingPackage).set(MediaMetrics.Property.CLIENT_NAME, caller).set(MediaMetrics.Property.DIRECTION, direction > 0? MediaMetrics.Value.UP : MediaMetrics.Value.DOWN).set(MediaMetrics.Property.EXTERNAL, hasExternalVolumeController? MediaMetrics.Value.YES : MediaMetrics.Value.NO).set(MediaMetrics.Property.FLAGS, flags).record();if (hasExternalVolumeController) {return;}final int streamType;synchronized (mForceControlStreamLock) {// Request lock in case mVolumeControlStream is changed by other thread.if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1streamType = mVolumeControlStream;} else {// 这里获取到,可能是活动状态的音频流,但是不确定,还有待进一步确认final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);final boolean activeForReal;if (maybeActiveStreamType == AudioSystem.STREAM_RING|| maybeActiveStreamType == AudioSystem.STREAM_NOTIFICATION) {activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0);} else {activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0);}if (activeForReal || mVolumeControlStream == -1) {streamType = maybeActiveStreamType;} else {// activeForReal为false并且mVolumeControlStream不为-1// 表示用户点击了音量进度条,这时候要操作修改的流类型为mVolumeControlStream对应的流类型streamType = mVolumeControlStream;}}}final boolean isMute = isMuteAdjust(direction);// 确保我们获取到的流类型是有效的ensureValidStreamType(streamType);// 将我们获取到的流,进行流映射,拿到最终需要操作的流类型final int resolvedStream = mStreamVolumeAlias[streamType];// Play sounds on STREAM_RING only.if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&resolvedStream != AudioSystem.STREAM_RING) {flags &= ~AudioManager.FLAG_PLAY_SOUND;}// For notifications/ring, show the ui before making any adjustments// Don't suppress mute/unmute requests// Don't suppress adjustments for single volume device// 通知和响铃,调整音量之前先显示UI。if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)&& !mIsSingleVolume) {direction = 0;flags &= ~AudioManager.FLAG_PLAY_SOUND;flags &= ~AudioManager.FLAG_VIBRATE;if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");}// 这里设置音量adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid,hasModifyAudioSettings, keyEventMode);}
suppressAdjustment:字面意思为抑制调整,为什么抑制调整呢,说白了,当我们没有显示音量的UI进度条的时候,不管我们是加音量还是减音量(注意:静音和解静音除外),这个时候都是先显示音量条,而不去改变音量的大小。所以当这个方法返回true的时候, direction = 0,这里direction为0就表示我们的操作为ADJUST_SAME,大家可以在AudioManager里面查看ADJUST_SAME的注释就知道这个操作表示只弹出UI但是不调整音量大小。
③adjustStreamVolume
protected void adjustStreamVolume(int streamType, int direction, int flags,String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,int keyEventMode) {//mUseFixedVolume表示使用固定音量,我们无法修改音量if (mUseFixedVolume) {return;}if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction+ ", flags=" + flags + ", caller=" + caller);ensureValidDirection(direction);ensureValidStreamType(streamType);boolean isMuteAdjust = isMuteAdjust(direction);if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {return;}// If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure// that the calling app have the MODIFY_PHONE_STATE permission.if (isMuteAdjust &&(streamType == AudioSystem.STREAM_VOICE_CALL ||streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)!= PackageManager.PERMISSION_GRANTED) {Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid="+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());return;}// If the stream is STREAM_ASSISTANT,// make sure that the calling app have the MODIFY_AUDIO_ROUTING permission.if (streamType == AudioSystem.STREAM_ASSISTANT &&mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)!= PackageManager.PERMISSION_GRANTED) {Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid="+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());return;}// use stream type alias here so that streams with same alias have the same behavior,// including with regard to silent mode control (e.g the use of STREAM_RING below and in// checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)//进行音频流的映射,拿到映射后的音频流int streamTypeAlias = mStreamVolumeAlias[streamType];//mStreamStates是一个存储VolumeStreamState类型的数组,保存着每个音频流的状态。//VolumeStreamState是AudioService的一个内部类,里面保存单个音频流的所有信息,比如流类型,音量大小,mute状态等。//并且相同的流类型,在不同的设备,大小也是不一样的(比如耳机和扬声器,媒体音量大小是不一样的)//这也是在VolumeStreamState里面去维护的。VolumeStreamState streamState = mStreamStates[streamTypeAlias];final int device = getDeviceForStream(streamTypeAlias);int aliasIndex = streamState.getIndex(device);boolean adjustVolume = true;int step;// skip a2dp absolute volume control request when the device// is not an a2dp deviceif (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {return;}// If we are being called by the system (e.g. hardware keys) check for current user// so we handle user restrictions correctly.if (uid == android.os.Process.SYSTEM_UID) {uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));}if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)!= AppOpsManager.MODE_ALLOWED) {return;}// reset any pending volume command// 清除掉任何待处理的音量命令synchronized (mSafeMediaVolumeStateLock) {mPendingVolumeCommand = null;}// 表示不是固定音量 flags &= ~AudioManager.FLAG_FIXED_VOLUME;// 如果是多媒体音量,并且是使用固定音量的设备if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {// 加上表示固定音量的flagflags |= AudioManager.FLAG_FIXED_VOLUME;// Always toggle between max safe volume and 0 for fixed volume devices where safe// volume is enforced, and max and 0 for the others.// This is simulated by stepping by the full allowed volume rangeif (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&mSafeMediaVolumeDevices.contains(device)) {step = safeMediaVolumeIndex(device);} else {step = streamState.getMaxIndex();}if (aliasIndex != 0) {aliasIndex = step;}} else {// convert one UI step (+/-1) into a number of internal units on the stream alias// 如果不是多媒体音量,或者是多媒体音量但是不是固定音量的设备时// 将音量值的步进量从源流类型变换到目标流类型下,由于不同的流类型的音量调节范围不同,所以这个转换是必需的step = rescaleStep(10, streamType, streamTypeAlias);}// // 情景模式的处理// If either the client forces allowing ringer modes for this adjustment,// or the stream type is one that is affected by ringer modesif (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||(streamTypeAlias == getUiSoundsStreamType())) {int ringerMode = getRingerModeInternal();// do not vibrate if already in vibrate mode// 如果已经是震动模式,则不进行震动if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {flags &= ~AudioManager.FLAG_VIBRATE;}// Check if the ringer mode handles this adjustment. If it does we don't// need to adjust the volume further.// 根据我们的操作来检查是否需要切换情景模式final int result = checkForRingerModeChange(aliasIndex, direction, step,streamState.mIsMuted, callingPackage, flags);adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;// If suppressing a volume adjustment in silent mode, display the UI hintif ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {flags |= AudioManager.FLAG_SHOW_SILENT_HINT;}// If suppressing a volume down adjustment in vibrate mode, display the UI hintif ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;}}// If the ringer mode or zen is muting the stream, do not change stream unless// it'll cause us to exit dnd// 勿扰模式if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {adjustVolume = false;}// 获取旧的音量大小int oldIndex = mStreamStates[streamType].getIndex(device);if (adjustVolume&& (direction != AudioManager.ADJUST_SAME) && (keyEventMode != VOL_ADJUST_END)) {mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);// 先处理静音调整if (isMuteAdjust) {boolean state;if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {state = !streamState.mIsMuted;} else {state = direction == AudioManager.ADJUST_MUTE;}if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {setSystemAudioMute(state);}for (int stream = 0; stream < mStreamStates.length; stream++) {if (streamTypeAlias == mStreamVolumeAlias[stream]) {if (!(readCameraSoundForced()&& (mStreamStates[stream].getStreamType()== AudioSystem.STREAM_SYSTEM_ENFORCED))) {// 这里获取当前流对应的VolumeStreamState实例,然后去调用mute方法// 最终也会到AudioSystem去调用native方法mStreamStates[stream].mute(state);}}}} else if ((direction == AudioManager.ADJUST_RAISE) &&!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {// 安全音量提示,音量增加的时候才会去检测Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);mVolumeController.postDisplaySafeVolumeWarning(flags);} else if (!isFullVolumeDevice(device)&& (streamState.adjustIndex(direction * step, device, caller,hasModifyAudioSettings)|| streamState.mIsMuted)) {// Post message to set system volume (it in turn will post a// message to persist).if (streamState.mIsMuted) {// Unmute the stream if it was previously mutedif (direction == AudioManager.ADJUST_RAISE) {// unmute immediately for volume upstreamState.mute(false);} else if (direction == AudioManager.ADJUST_LOWER) {if (mIsSingleVolume) {sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);}}}// 设置音量到底层sendMsg(mAudioHandler,MSG_SET_DEVICE_VOLUME,SENDMSG_QUEUE,device,0,streamState,0);}int newIndex = mStreamStates[streamType].getIndex(device);// Check if volume update should be send to AVRCPif (streamTypeAlias == AudioSystem.STREAM_MUSIC&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {if (DEBUG_VOL) {Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="+ newIndex + "stream=" + streamType);}mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10);}// Check if volume update should be send to Hearing Aidif (device == AudioSystem.DEVICE_OUT_HEARING_AID) {// only modify the hearing aid attenuation when the stream to modify matches// the one expected by the hearing aidif (streamType == getHearingAidStreamType()) {if (DEBUG_VOL) {Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index="+ newIndex + " stream=" + streamType);}mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);}}// Check if volume update should be sent to Hdmi system audio.if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);}}final int newIndex = mStreamStates[streamType].getIndex(device);if (adjustVolume) {synchronized (mHdmiClientLock) {if (mHdmiManager != null) {// mHdmiCecSink true => mHdmiPlaybackClient != nullif (mHdmiCecSink&& mHdmiCecVolumeControlEnabled&& streamTypeAlias == AudioSystem.STREAM_MUSIC// vol change on a full volume device&& isFullVolumeDevice(device)) {int keyCode = KeyEvent.KEYCODE_UNKNOWN;switch (direction) {case AudioManager.ADJUST_RAISE:keyCode = KeyEvent.KEYCODE_VOLUME_UP;break;case AudioManager.ADJUST_LOWER:keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;break;case AudioManager.ADJUST_TOGGLE_MUTE:keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;break;default:break;}if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {final long ident = Binder.clearCallingIdentity();try {final long time = java.lang.System.currentTimeMillis();switch (keyEventMode) {case VOL_ADJUST_NORMAL:mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, true);mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, false);break;case VOL_ADJUST_START:mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, true);break;case VOL_ADJUST_END:mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, false);break;default:Log.e(TAG, "Invalid keyEventMode " + keyEventMode);}} finally {Binder.restoreCallingIdentity(ident);}}}if (streamTypeAlias == AudioSystem.STREAM_MUSIC&& (oldIndex != newIndex || isMuteAdjust)) {maybeSendSystemAudioStatusCommand(isMuteAdjust);}}}}// 通知外界音量发生变化sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device);}
④sendVolumeUpdate
// UI update and Broadcast Intentprotected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags, int device){streamType = mStreamVolumeAlias[streamType];if (streamType == AudioSystem.STREAM_MUSIC) {flags = updateFlagsForTvPlatform(flags);if (isFullVolumeDevice(device)) {flags &= ~AudioManager.FLAG_SHOW_UI;}}mVolumeController.postVolumeChanged(streamType, flags);}
3、AudioManager
/*** Increase the ringer volume.** @see #adjustVolume(int, int)* @see #adjustStreamVolume(int, int, int)*/public static final int ADJUST_RAISE = 1;/*** Decrease the ringer volume.** @see #adjustVolume(int, int)* @see #adjustStreamVolume(int, int, int)*/public static final int ADJUST_LOWER = -1;/*** Maintain the previous ringer volume. This may be useful when needing to* show the volume toast without actually modifying the volume.** @see #adjustVolume(int, int)* @see #adjustStreamVolume(int, int, int)*/public static final int ADJUST_SAME = 0;/*** Mute the volume. Has no effect if the stream is already muted.** @see #adjustVolume(int, int)* @see #adjustStreamVolume(int, int, int)*/public static final int ADJUST_MUTE = -100;/*** Unmute the volume. Has no effect if the stream is not muted.** @see #adjustVolume(int, int)* @see #adjustStreamVolume(int, int, int)*/public static final int ADJUST_UNMUTE = 100;/*** Toggle the mute state. If muted the stream will be unmuted. If not muted* the stream will be muted.** @see #adjustVolume(int, int)* @see #adjustStreamVolume(int, int, int)*/public static final int ADJUST_TOGGLE_MUTE = 101;
这篇关于Android 11 SystemUI 启动流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!