1.启动流程

SystemUI启动是在SystemServer进程之后启动的,android系统启动流程依次是:

从Boot RAM->BootLoader->Kenel->Init->Zygote->SystemServer->Launcher,SystemUI是在SystemServer进程中启动的,SystemServer是Zygote进程fork出来的,SystemServer.java文件查找路径方法,切换到工程目录下,

find ./ -name *SystemServer.java*

找到SystemServer之后,SystemServer的main方法如下:

//文件路径:
//./frameworks/base/services/java/com/android/server/SystemServer.java
/*** The main entry point from zygote.*/
public static void main(String[] args) {new SystemServer().run();
}

这里面直接初始化后,调用run方法,在run方法里面启动的有三个Service的方法

// Start services.
try {t.traceBegin("StartServices");startBootstrapServices(t);//启动引导服务startCoreServices(t);//启动核心服务startOtherServices(t);//启动其他服务
} catch (Throwable ex) {Slog.e("System", "******************************************");Slog.e("System", "************ Failure starting system services", ex);throw ex;
} finally {t.traceEnd(); // StartServices
}

SystemUIService的启动是在startOtherService里面,

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);context.startServiceAsUser(intent, UserHandle.SYSTEM);windowManager.onSystemUiStarted();
}

从localService里面拿到Service的名字,然后启动,拿到的Service名字是SystemUIService,SystemUIService执行onCreate方法,

//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
// Start all of SystemUI
((SystemUIApplication) getApplication()).startServicesIfNeeded();
​
// Finish initializing dump logic
mLogBufferFreezer.attach(mBroadcastDispatcher);
​
// For debugging RescueParty
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {throw new RuntimeException();
}
​
if (Build.IS_DEBUGGABLE) {// b/71353150 - looking for leaked binder proxiesBinderInternal.nSetBinderProxyCountEnabled(true);BinderInternal.nSetBinderProxyCountWatermarks(1000,900);BinderInternal.setBinderProxyCountCallback(new BinderInternal.BinderProxyLimitListener() {@Overridepublic void onLimitReached(int uid) {Slog.w(SystemUIApplication.TAG,"uid " + uid + " sent too many Binder proxies to uid "+ Process.myUid());}}, mMainHandler);
}
​
// Bind the dump service so we can dump extra info during a bug report
startServiceAsUser(
new Intent(getApplicationContext(), SystemUIAuxiliaryDumpService.class),
UserHandle.SYSTEM);

前面是SystemUIService启动的方法,后面都是调试状态下的输出,等于是直接调用SystemUIApplication.java的startServicesIfNeeded方法,

//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
/*** Makes sure that all the SystemUI services are running. If they are already running, this is a* no-op. This is needed to conditinally start all the services, as we only need to have it in* the main process.* <p>This method must only be called from the main thread.</p>*/
​
public void startServicesIfNeeded() {String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}

这个Service的列表是从SystemUIFactory里面取到的,

//文件路径
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
/** Returns the list of system UI components that should be started. */
public String[] getSystemUIServiceComponents(Resources resources) {return resources.getStringArray(R.array.config_systemUIServiceComponents);
}

从res里面的array里面拿到需要启动的服务,这个服务的数组分为两种,一种是正常的res里面的value,另外一种是tv版的服务数组列表,内容如下:

//文件路径:
//frameworks/base/packages/SystemUI/res/values-television/config.xml
<!-- SystemUI Services: The classes of the stuff to start. -->
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.util.NotificationChannels</item>//通知
<item>com.android.systemui.volume.VolumeUI</item>//声音
<item>com.android.systemui.stackdivider.Divider</item>//分屏
<item>com.android.systemui.statusbar.tv.TvStatusBar</item>//电视状态栏
<item>com.android.systemui.usb.StorageNotification</item>//存储通知
<item>com.android.systemui.power.PowerUI</item>//电量界面
<item>com.android.systemui.media.RingtonePlayer</item>//铃声播放器
<item>com.android.systemui.keyboard.KeyboardUI</item>//键盘界面
<item>com.android.systemui.pip.PipUI</item>//图片窗口中的图片
<item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>//快捷分发器
<item>@string/config_systemUIVendorServiceComponent</item>//供应商服务
<item>com.android.systemui.SliceBroadcastRelayHandler</item>//允许打开设置app
<item>com.android.systemui.SizeCompatModeActivityController</item>//允许重启activity
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>//即时应用程序通知
<item>com.android.systemui.toast.ToastUI</item>//弹窗提示
</string-array>

需要启动的服务大概如上述所示,后面详细说明这些服务,

//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
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;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]);mServices[i].start();log.traceEnd();
​// Warn if initialization of component takes too longti = System.currentTimeMillis() - ti;if (ti > 1000) {Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");}//如果android系统已经启动的话,会有回调if (mBootCompleteCache.isBootComplete()) {mServices[i].onBootCompleted();}
​//这里是注册dump,用来使用命令dump时使用dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);}mRootComponent.getInitController().executePostInitTasks();log.traceEnd();
​mServicesStarted = true;}

在这个方法里面启动SystemUI的相关内容的,把存放进数组的SystemUI通过反射的形式初始化对象,然后每个都调用start方法启动,启动相关的SystemUI内容,之后把这些可以服务都通过dumpManager注册dump的服务,启动流程大致如此。

2.注册dump介绍

之前的startServicesIfNeeded方法最后一行里面进行了注册,注册的代码如下:

//文件路径
//./frameworks/base/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
/*** Register a dumpable to be called during a bug report. The dumpable will be called during the* CRITICAL section of the bug report, so don't dump an excessive amount of stuff here.** @param name The name to register the dumpable under. This is typically the qualified class* name of the thing being dumped (getClass().getName()), but can be anything as long as it* doesn't clash with an existing registration.*/
@Synchronized
fun registerDumpable(name: String, module: Dumpable) {if (!canAssignToNameLocked(name, module)) {throw IllegalArgumentException("'$name' is already registered")}
​dumpables[name] = RegisteredDumpable(name, module)
}

这个里面存放的是MutableMap<String, RegisteredDumpable<Dumpable>>类型的map数据,其中key在存储时表示的SystemUI的className,里面记录的是config.xml里面service,这些service的顶层父类都是SystemUI,SystemUI是一个实现了Dumpable接口的抽象类,这个map的value是这些service的实体对象,就是SystemUIApplication中通过Provider中拿到的对象。

其中Dump接口代码如下:

//文件路径
//./frameworks/base/services/core/java/com/android/server/soundtrigger_middleware/Dumpable.java
/*** Implemented by classes who want to be in:*   {@code adb shell dumpsys activity service com.android.systemui}** @see DumpManager*/
public interface Dumpable {
​/*** Called when it's time to dump the internal state* @param fd A file descriptor.* @param pw Where to write your dump to.* @param args Arguments.*/void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args);
}

代码注释中已经说明,实现这个接口主要是为了通过“adb shell dumpsys activity service com.android.systemui”这个命令dump内容,可以通过cmd控制台输出这个命令看一下输出,这个输出会根据子类重写的这个方法把相关的参数输出出来,从过滤出来的日志能够查到。

3.SystemUI不同的UI内容

1.简述

SystemUI是顶层父类,实现了Dumpable接口用于dump相关内容,其他所有的SystemUI全部都是继承自这个类,

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) {}
​//需要dump时使用@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);}
}

2.SystemUI分类

2.1通知管理:NotificationChannels

这个类是顶层通知栏类似于推送消息时,展示的内容,调用这个start方法之后,把需要用到的channelID创建出来,

final NotificationManager nm = context.getSystemService(NotificationManager.class);
final NotificationChannel batteryChannel = new NotificationChannel(BATTERY,context.getString(R.string.notification_channel_battery),NotificationManager.IMPORTANCE_MAX);
final String soundPath = Settings.Global.getString(context.getContentResolver(),Settings.Global.LOW_BATTERY_SOUND);
batteryChannel.setSound(Uri.parse("file://" + soundPath), new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT).build());
batteryChannel.setBlockable(true);
​
final NotificationChannel alerts = new NotificationChannel(ALERTS,context.getString(R.string.notification_channel_alerts),NotificationManager.IMPORTANCE_HIGH);
​
final NotificationChannel general = new NotificationChannel(GENERAL,context.getString(R.string.notification_channel_general),NotificationManager.IMPORTANCE_MIN);
​
final NotificationChannel storage = new NotificationChannel(STORAGE,context.getString(R.string.notification_channel_storage),isTv(context)? NotificationManager.IMPORTANCE_DEFAULT: NotificationManager.IMPORTANCE_LOW);
​
final NotificationChannel hint = new NotificationChannel(HINTS,context.getString(R.string.notification_channel_hints),NotificationManager.IMPORTANCE_DEFAULT);
// No need to bypass DND.
​
nm.createNotificationChannels(Arrays.asList(alerts,general,storage,createScreenshotChannel(context.getString(R.string.notification_channel_screenshot),nm.getNotificationChannel(SCREENSHOTS_LEGACY)),batteryChannel,hint
));
​
// Delete older SS channel if present.
// Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O.
// This line can be deleted in Q.
nm.deleteNotificationChannel(SCREENSHOTS_LEGACY);
​
​
if (isTv(context)) {// TV specific notification channel for TV PIP controls.// Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest// priority, so it can be shown in all times.nm.createNotificationChannel(new NotificationChannel(TVPIP,context.getString(R.string.notification_channel_tv_pip),NotificationManager.IMPORTANCE_MAX));
}

分别创建了电量,alerts,普通类型的消息,存储,hint,截屏,TVPIP的通知渠道,只是创建好,但是什么时候通知调用?

2.2音量UI

VolumeUI.java 是控制音量弹出框的,调用start方法后,设置是否可用的开关和警告提示的开关,主要流程涉及到的类包括:VolumeDialogComponent.java,VolumeDialog.java,VolumeDialogImpl.java,VolumeDialogController.java,VolumeDialogControllerImpl.java,AudioManager.java,大概流程如下,VolumeDialogComponent是一个dialog的组件,

@Inject
public 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).withDefault(this::createDefault)//调用这个方法初始化.withCallback(dialog -> {if (mDialog != null) {mDialog.destroy();}mDialog = dialog;mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);//init设置一些内容}).build();applyConfiguration();Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,VOLUME_SILENT_DO_NOT_DISTURB);
}

调用create创建dialog,

protected VolumeDialog createDefault() {VolumeDialogImpl impl = new VolumeDialogImpl(mContext);impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);impl.setAutomute(true);impl.setSilentMode(false);return impl;
}

创建出来一个VolumeDialogImpl对象,VolumeDialogImpl是VolumeDialog接口的实现类,然后调用dialog的初始化,初始化的代码如下,

public void init(int windowType, Callback callback) {initDialog();
​mAccessibility.init();
​mController.addCallback(mControllerCallbackH, mHandler);mController.getState();
​Dependency.get(ConfigurationController.class).addCallback(this);
}

其中initDialog()方法是调用这个dialog初始化的内容,这个里面是比较典型的dialog开发时的代码,不过也非常复杂,setContentView时传入的是R.layout.volume_dialog,这个layout是一个FrameLayout和LinearLayout的组合,其中比较重要的地方,

<LinearLayoutandroid:id="@+id/volume_dialog_rows"android:layout_width="wrap_content"android:layout_height="wrap_content"android:minWidth="@dimen/volume_dialog_panel_width"android:gravity="center"android:orientation="horizontal"android:paddingRight="@dimen/volume_dialog_stream_padding"android:paddingLeft="@dimen/volume_dialog_stream_padding"><!-- volume rows added and removed here! :-) --></LinearLayout>

这里面会把不同音量控制的开关添加到这个linearLayout里面,在initDialog中最终会调用addRow()方法,根据是否是单声道,如果不是单声道的话会添加很多的View到这个linearLayout里面,分为STREAM_ACCESSIBILITY,STREAM_MUSIC(音乐大小),STREAM_RING(铃声),STREAM_ALARM(系统铃声),STREAM_VOICE_CALL(通话声音),STREAM_BLUETOOTH_SCO(蓝牙,这个不能用),STREAM_SYSTEM(系统声音),这些需要控制的音量全部都会通过addRow()添加进来,addRow()方法如下,

private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,boolean defaultStream, boolean dynamic) {if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);VolumeRow row = new VolumeRow();initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);mDialogRowsView.addView(row.view);mRows.add(row);
}

这个里面会先调用初始化,其中VolumeRow这个类是个组合类,他是对这隔单个音量的信息的封装,

    
private static class VolumeRow {private View view;private TextView header;private ImageButton icon;private SeekBar slider;private int stream;private StreamState ss;private long userAttempt;  // last user-driven slider changeprivate boolean tracking;  // tracking slider touchprivate int requestedLevel = -1;  // pending user-requested level via progress changedprivate int iconRes;private int iconMuteRes;private boolean important;private boolean defaultStream;private ColorStateList cachedTint;private int iconState;  // from Eventsprivate ObjectAnimator anim;  // slider progress animation for non-touch-related updatesprivate int animTargetProgress;private int lastAudibleLevel = 1;private FrameLayout dndIcon;}

在调用init初始化时,row中的view通过inflate(R.layout.volume_dialog_row)的形式构造一个View,xml里面有seekBar,这个SeekBar会注册监听器,是通过VolumeSeekBarChangeListener这个内部类实现的,具体回调的内容包括触摸反馈,状态改变,然后做相应的处理,然后动态的添加到volume_dialog_rows指定的viewGroup里面,还需要有一个List<VolumeRow> mRows的对象控制添加的内容,这部分大概是音量UI的View层面的内容,音量这部分内容使用的是MVP的架构,这个dialog属于View层面的内容。

VolumeDialogControllerImpl是Presenter层面的内容,这个类是VolumeDialogController接口的实现,也实现了dump接口用于dump需要输出的内容,在初始化时拿到AudioManager的服务,然后互相通信,AudioManager是音量的具体Model层,他负责音量数据源,他们之间的通信关系如下,

2.3Divider分屏

Divider是处理分屏的业务,构造方法如下:

public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy,DisplayController displayController, SystemWindows systemWindows,DisplayImeController imeController, Handler handler,KeyguardStateController keyguardStateController, TransactionPool transactionPool) {super(context);mDisplayController = displayController;mSystemWindows = systemWindows;//这个参数是当前的window,通过他处理添加分屏,删除分屏的操作mImeController = imeController;mHandler = handler;mKeyguardStateController = keyguardStateController;mRecentsOptionalLazy = recentsOptionalLazy;mForcedResizableController = new ForcedResizableInfoActivityController(context, this);mTransactionPool = transactionPool;mWindowManagerProxy = new WindowManagerProxy(mTransactionPool, mHandler);mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler);
}

其中mSystemWindows是一个SystemWindows类的对象,主要就是通过他来控制分屏处理的,start方法如下:

@Override
public void start() {mWindowManager = new DividerWindowManager(mSystemWindows);mDisplayController.addDisplayWindowListener(this);// Hide the divider when keyguard is showing. Even though keyguard/statusbar is above// everything, it is actually transparent except for notifications, so we still need to// hide any surfaces that are below it.// TODO(b/148906453): Figure out keyguard dismiss animation for divider view.mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {@Overridepublic void onUnlockedChanged() {
​}
​@Overridepublic void onKeyguardShowingChanged() {if (!isSplitActive() || mView == null) {return;}mView.setHidden(mKeyguardStateController.isShowing());if (!mKeyguardStateController.isShowing()) {mImePositionProcessor.updateAdjustForIme();}}
​@Overridepublic void onKeyguardFadingAwayChanged() {
​}});// Don't initialize the divider or anything until we get the default display.
}

start方法中构造了一个DividerWindowManager的对象,分屏通过这个windowManager操作添加屏幕,或者删除屏幕的操作,这里面还加了一个键盘的回调,从注释上的意思是说隐藏分屏当键盘展示的时候,真正负责处理添加删除的是DividerWindowManager,DividerWindowManager的构造方法入参是mSystemWindows,Divider类中方法比较多,其中addDivider()和removeDivider()的代码如下:

private void addDivider(Configuration configuration) {Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());mView = (DividerView)LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout,mImePositionProcessor, mWindowManagerProxy);mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */);final int size = dctx.getResources().getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_thickness);final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;final int width = landscape ? size : displayLayout.width();final int height = landscape ? displayLayout.height() : size;mWindowManager.add(mView, width, height, mContext.getDisplayId());
}
​
private void removeDivider() {if (mView != null) {mView.onDividerRemoved();}mWindowManager.remove();
}

其实都是通过windowManager进行的操作,每次add都是inflate出来一个view,通过windowManager添加进去,每次操作时都是先removeDivider然后再重新addDivider(),而removeDivider()操作在多处调用,其中一处如下:

private void update(Configuration configuration) {final boolean isDividerHidden = mView != null && mKeyguardStateController.isShowing();
​removeDivider();addDivider(configuration);
​if (mMinimized) {mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */);updateTouchable();}mView.setHidden(isDividerHidden);
}

先删除,再添加,其中操作这个VIew的时候,这个VIew的名字是DividerView,这个是个自定义View,里面处理拖拽,触摸等一系列操作。

2.4TvStatusBar电视状态栏

TvStatusBar也是一个状态栏的通知,主要处理隐式意图跳转的逻辑,start方法如下:

@Override
public void start() {final IStatusBarService barService = IStatusBarService.Stub.asInterface(ServiceManager.getService(Context.STATUS_BAR_SERVICE));mCommandQueue.addCallback(this);try {barService.registerStatusBar(mCommandQueue);} catch (RemoteException ex) {// If the system process isn't there we're doomed anyway.}
}

这里面进行了一个绑定服务,添加了一个回调,其中mCommandQueue是构造方法传递的参数,添加完回调后主要是为了方便通知,mCommandQueue是CommandQueue.java的实例对象,这个类比较庞大,里面有一个名为H的handler用于发送消息,TvStatusBar实现的接口是CommandQueue.Callbacks,这个类就是CommandQueue.java的一个接口,定义了非常多的方法用于循环通知回调,列举一部分大概确认下内容:

default void setIcon(String slot, StatusBarIcon icon) { }
default void removeIcon(String slot) { }
​
/*** Called to notify that disable flags are updated.* @see IStatusBar#disable(int, int, int).** @param displayId The id of the display to notify.* @param state1 The combination of following DISABLE_* flags:* @param state2 The combination of following DISABLE2_* flags:* @param animate {@code true} to show animations.*/
default void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,boolean animate) { }
default void animateExpandNotificationsPanel() { }
default void animateCollapsePanels(int flags, boolean force) { }
default void togglePanel() { }
default void animateExpandSettingsPanel(String obj) { }
​
/*** Called to notify IME window status changes.** @param displayId The id of the display to notify.* @param token IME token.* @param vis IME visibility.* @param backDisposition Disposition mode of back button. It should be one of below flags:* @param showImeSwitcher {@code true} to show IME switch button.*/
default void setImeWindowStatus(int displayId, IBinder token,  int vis,@BackDispositionMode int backDisposition, boolean showImeSwitcher) { }

而TvStatusBar只实现了其中两个方法,分别是是animateExpandNotificationsPanel()和startAssist(),animateExpandNotificationsPanel用于启动系统的activity,startAssist用于打开assist里面的内容。在CommandQueue中的H中调用animateExpandNotificationsPanel方法,

case MSG_EXPAND_NOTIFICATIONS:
for (int i = 0; i < mCallbacks.size(); i++) {mCallbacks.get(i).animateExpandNotificationsPanel();
}

发现这个MSG_EXPAND_NOTIFICATIONS类型的消息调用的方法在本模块没有被调用到:

public void animateExpandNotificationsPanel() {synchronized (mLock) {mHandler.removeMessages(MSG_EXPAND_NOTIFICATIONS);mHandler.sendEmptyMessage(MSG_EXPAND_NOTIFICATIONS);}
}

2.5StorageNotification存储通知

StorageNotification.java主要是处理存储相关的通知,其中start方法的内容如下:

@Override
public void start() {mNotificationManager = mContext.getSystemService(NotificationManager.class);//获取通知服务
​mStorageManager = mContext.getSystemService(StorageManager.class);mStorageManager.registerListener(mListener);//这里注册了一个监听器
​mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);//mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);//完成的广播
​// Kick current state into placefinal List<DiskInfo> disks = mStorageManager.getDisks();for (DiskInfo disk : disks) {onDiskScannedInternal(disk, disk.volumeCount);}
​final List<VolumeInfo> vols = mStorageManager.getVolumes();for (VolumeInfo vol : vols) {onVolumeStateChangedInternal(vol);}
​mContext.getPackageManager().registerMoveCallback(mMoveCallback, new Handler());//这里也加了个回调
​updateMissingPrivateVolumes();
}

主要看注册的监听器,其中mListener是个检测存储状态改变的回调类,

private final StorageEventListener mListener = new StorageEventListener() {@Overridepublic void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {onVolumeStateChangedInternal(vol);}
​@Overridepublic void onVolumeRecordChanged(VolumeRecord rec) {// Avoid kicking notifications when getting early metadata before// mounted. If already mounted, we're being kicked because of a// nickname or init'ed change.final VolumeInfo vol = mStorageManager.findVolumeByUuid(rec.getFsUuid());if (vol != null && vol.isMountedReadable()) {onVolumeStateChangedInternal(vol);}}
​@Overridepublic void onVolumeForgotten(String fsUuid) {// Stop annoying the usermNotificationManager.cancelAsUser(fsUuid, SystemMessage.NOTE_STORAGE_PRIVATE,UserHandle.ALL);}
​@Overridepublic void onDiskScanned(DiskInfo disk, int volumeCount) {onDiskScannedInternal(disk, volumeCount);}
​@Overridepublic void onDiskDestroyed(DiskInfo disk) {onDiskDestroyedInternal(disk);}
};

状态改变的时候会调用onVolumeStateChangedInternal这个方法,继续调用:

private void onVolumeStateChangedInternal(VolumeInfo vol) {switch (vol.getType()) {case VolumeInfo.TYPE_PRIVATE:onPrivateVolumeStateChangedInternal(vol);break;case VolumeInfo.TYPE_PUBLIC:onPublicVolumeStateChangedInternal(vol);break;}
}

继续调用onPublicVolumeStateChangedInternal,在这个方法里面会处理各种不同状态的通知:

private void onPublicVolumeStateChangedInternal(VolumeInfo vol) {Log.d(TAG, "Notifying about public volume: " + vol.toString());
​// Volume state change event may come from removed user, in this case, mountedUserId will// equals to UserHandle.USER_NULL (-10000) which will do nothing when call cancelAsUser(),// but cause crash when call notifyAsUser(). Here we return directly for USER_NULL, and// leave all notifications belong to removed user to NotificationManagerService, the latter// will remove all notifications of the removed user when handles user stopped broadcast.if (isAutomotive() && vol.getMountUserId() == UserHandle.USER_NULL) {Log.d(TAG, "Ignore public volume state change event of removed user");return;}
​final Notification notif;switch (vol.getState()) {case VolumeInfo.STATE_UNMOUNTED://卸载notif = onVolumeUnmounted(vol);break;case VolumeInfo.STATE_CHECKING://检测notif = onVolumeChecking(vol);break;case VolumeInfo.STATE_MOUNTED:case VolumeInfo.STATE_MOUNTED_READ_ONLY://挂载notif = onVolumeMounted(vol);break;case VolumeInfo.STATE_FORMATTING://对其notif = onVolumeFormatting(vol);break;case VolumeInfo.STATE_EJECTING://拒绝notif = onVolumeEjecting(vol);break;case VolumeInfo.STATE_UNMOUNTABLE://不可被卸载的notif = onVolumeUnmountable(vol);break;case VolumeInfo.STATE_REMOVED://移除notif = onVolumeRemoved(vol);break;case VolumeInfo.STATE_BAD_REMOVAL://错误的移除notif = onVolumeBadRemoval(vol);break;default:notif = null;break;}
​if (notif != null) {mNotificationManager.notifyAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC,notif, UserHandle.of(vol.getMountUserId()));} else {mNotificationManager.cancelAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC,UserHandle.of(vol.getMountUserId()));}
}

处理完成之后用notificationManager做通知,后面会有对应的方法做通知的生成,这个类里面也会有很多生成PendingIntent的方法,主要是为了配合生成notificaiton的点击跳转的intent,方法名大致如下:buildInitPendingIntent(),buildUnmountPendingIntent(),buildBrowsePendingIntent(),buildVolumeSettingsPendingIntent(),buildSnoozeIntent(),buildForgetPendingIntent(),buildWizardMigratePendingIntent(),buildWizardMovePendingIntent(),buildWizardReadyPendingIntent(),这些方法名字根据英文翻译大概能确认要生成的intent的内容。

2.6PowerUI电量界面

PowerUI.java是处理关于电量部分得相关内容,都会调用start启用这个服务,其中start的代码如下:

public void start() {mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();mWarnings = Dependency.get(WarningsUI.class);//通过这个更新UI的相关内容mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);mLastConfiguration.setTo(mContext.getResources().getConfiguration());
​ContentObserver obs = new ContentObserver(mHandler) {@Overridepublic void onChange(boolean selfChange) {updateBatteryWarningLevels();}};final ContentResolver resolver = mContext.getContentResolver();resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),false, obs, UserHandle.USER_ALL);updateBatteryWarningLevels();mReceiver.init();//调用receiver初始化接收这些状态
​// Check to see if we need to let the user know that the phone previously shut down due// to the temperature being too high.showWarnOnThermalShutdown();
​// Register an observer to configure mEnableSkinTemperatureWarning and perform the// registration of skin thermal event listener upon Settings change.resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.SHOW_TEMPERATURE_WARNING),false /*notifyForDescendants*/,new ContentObserver(mHandler) {@Overridepublic void onChange(boolean selfChange) {doSkinThermalEventListenerRegistration();}});// Register an observer to configure mEnableUsbTemperatureAlarm and perform the// registration of usb thermal event listener upon Settings change.resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.SHOW_USB_TEMPERATURE_ALARM),false /*notifyForDescendants*/,new ContentObserver(mHandler) {@Overridepublic void onChange(boolean selfChange) {doUsbThermalEventListenerRegistration();}});initThermalEventListeners();mCommandQueue.addCallback(this);
}

初始化时调用showWarnOnThermalShutdown这个方法检查上次关机是不是因为温度太高,注册手机温度过高的回调,doSkinThermalEventListenerRegistration方法做手机温度过高的处理,调用doUsbThermalEventListenerRegistration这个方法处理USB接口温度过高的处理,其中receiver接受做的是电量的逻辑处理,如果需要用到处理UI的相关内容,是通过mWarnings做的界面更新内容,mWarnings是一个抽象接口,

/*** The interface to allow PowerUI to communicate with whatever implementation of WarningsUI* is being used by the system.*/
public interface WarningsUI {
​/*** Updates battery and screen info for determining whether to trigger battery warnings or* not.* @param batteryLevel The current battery level* @param bucket The current battery bucket* @param screenOffTime How long the screen has been off in millis*/void update(int batteryLevel, int bucket, long screenOffTime);
​void dismissLowBatteryWarning();
​void showLowBatteryWarning(boolean playSound);
​void dismissInvalidChargerWarning();
​void showInvalidChargerWarning();
​void updateLowBatteryWarning();
​boolean isInvalidChargerWarningShowing();
​void dismissHighTemperatureWarning();
​void showHighTemperatureWarning();
​/*** Display USB port overheat alarm*/void showUsbHighTemperatureAlarm();
​void showThermalShutdownWarning();
​void dump(PrintWriter pw);
​void userSwitched();
​/*** Updates the snapshot of battery state used for evaluating battery warnings* @param snapshot object containing relevant values for making battery warning decisions.*/void updateSnapshot(BatteryStateSnapshot snapshot);
}

他的实现类是PowerNotificationWarnings.java,这个里面做实际的电量界面更新的内容,其中的方法名字比较好理解:

showThermalShutdownWarning()
showUsbHighTemperatureAlarm()
showUsbHighTemperatureAlarmInternal()
updateLowBatteryWarning()
dismissLowBatteryWarning()
hasBatterySettings()
dismissInvalidChargerWarning()
showInvalidChargerWarning()
dismissAutoSaverSuggestion()
//还有需要比较好理解的方法名,能够直译过来方便理解,仅列举这么多

这个PowerNotificationWarnings的初始化是在PowerUI里面初始化的,通过Dependency拿到的一个单例对象。

2.7RingtonePlayer铃声播放器

RingtonePlayer是处理铃声播放器的类,start方法如下:

@Override
public void start() {mAsyncPlayer.setUsesWakeLock(mContext);
​mAudioService = IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));try {mAudioService.setRingtonePlayer(mCallback);} catch (RemoteException e) {Log.e(TAG, "Problem registering RingtonePlayer: " + e);}
}

通过AIDL获取完AudioService之后设置了一个回调,mCallback处理铃声的相关逻辑:

private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {@Overridepublic void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)throws RemoteException {playWithVolumeShaping(token, uri, aa, volume, looping, null);}@Overridepublic void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)throws RemoteException {if (LOGD) {Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="+ Binder.getCallingUid() + ")");}Client client;synchronized (mClients) {client = mClients.get(token);if (client == null) {final UserHandle user = Binder.getCallingUserHandle();client = new Client(token, uri, user, aa, volumeShaperConfig);token.linkToDeath(client, 0);mClients.put(token, client);}}client.mRingtone.setLooping(looping);client.mRingtone.setVolume(volume);client.mRingtone.play();}
​@Overridepublic void stop(IBinder token) {if (LOGD) Log.d(TAG, "stop(token=" + token + ")");Client client;synchronized (mClients) {client = mClients.remove(token);}if (client != null) {client.mToken.unlinkToDeath(client, 0);client.mRingtone.stop();}}
​@Overridepublic boolean isPlaying(IBinder token) {if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");Client client;synchronized (mClients) {client = mClients.get(token);}if (client != null) {return client.mRingtone.isPlaying();} else {return false;}}
​@Overridepublic void setPlaybackProperties(IBinder token, float volume, boolean looping) {Client client;synchronized (mClients) {client = mClients.get(token);}if (client != null) {client.mRingtone.setVolume(volume);client.mRingtone.setLooping(looping);}// else no client for token when setting playback properties but will be set at play()}
​@Overridepublic void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");if (Binder.getCallingUid() != Process.SYSTEM_UID) {throw new SecurityException("Async playback only available from system UID.");}if (UserHandle.ALL.equals(user)) {user = UserHandle.SYSTEM;}mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);}
​@Overridepublic void stopAsync() {if (LOGD) Log.d(TAG, "stopAsync()");if (Binder.getCallingUid() != Process.SYSTEM_UID) {throw new SecurityException("Async playback only available from system UID.");}mAsyncPlayer.stop();}
​@Overridepublic String getTitle(Uri uri) {final UserHandle user = Binder.getCallingUserHandle();return Ringtone.getTitle(getContextForUser(user), uri,false /*followSettingsUri*/, false /*allowRemote*/);}
​@Overridepublic ParcelFileDescriptor openRingtone(Uri uri) {final UserHandle user = Binder.getCallingUserHandle();final ContentResolver resolver = getContextForUser(user).getContentResolver();
​// Only open the requested Uri if it's a well-known ringtone or// other sound from the platform media store, otherwise this opens// up arbitrary access to any file on external storage.if (uri.toString().startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {try (Cursor c = resolver.query(uri, new String[] {MediaStore.Audio.AudioColumns.IS_RINGTONE,MediaStore.Audio.AudioColumns.IS_ALARM,MediaStore.Audio.AudioColumns.IS_NOTIFICATION}, null, null, null)) {if (c.moveToFirst()) {if (c.getInt(0) != 0 || c.getInt(1) != 0 || c.getInt(2) != 0) {try {return resolver.openFileDescriptor(uri, "r");} catch (IOException e) {throw new SecurityException(e);}}}}}throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);}
}

如上所示,这个方法名命名的比较容易理解,当调用play时,从mClients中取到binder对应的client,mClients是一个HashMap<IBinder, Client>,用于保存和客户端的连接的对象,Client里面通过mRingtone做声音的处理,Ringtone就是快速设置音量的类,里面的方法名如下:

setAudioAttributes()
setLooping()
setVolume()
setUri(Uri uri)
play()
stop()

但是这个类上层是无法操作的,试过之后,这个类只能在系统里面操作。

2.8KeyboardUI键盘界面

KeyboardUI是在androidTV版配置下才存在的服务,主要是为了处理蓝牙键盘,这个蓝牙键盘在手机上是不可以使用的,里面通过蓝牙连接外设键盘,通过广播的形式处理锁屏,关机的对话框,SystemUIDialog是一个alertDialog,状态改变时会设置不同的window的属性,内部的receiver代码如下:

/*** Registers a listener that dismisses the given dialog when it receives* the screen off / close system dialogs broadcast.* <p>* <strong>Note:</strong> Don't call dialog.setOnDismissListener() after* calling this because it causes a leak of BroadcastReceiver.** @param dialog The dialog to be associated with the listener.*/
public static void registerDismissListener(Dialog dialog) {DismissReceiver dismissReceiver = new DismissReceiver(dialog);dialog.setOnDismissListener(d -> dismissReceiver.unregister());dismissReceiver.register();
}
​
private static class DismissReceiver extends BroadcastReceiver {private static final IntentFilter INTENT_FILTER = new IntentFilter();static {INTENT_FILTER.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);}
​private final Dialog mDialog;private boolean mRegistered;private final BroadcastDispatcher mBroadcastDispatcher;
​DismissReceiver(Dialog dialog) {mDialog = dialog;mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);}
​void register() {mBroadcastDispatcher.registerReceiver(this, INTENT_FILTER, null, UserHandle.CURRENT);mRegistered = true;}
​void unregister() {if (mRegistered) {mBroadcastDispatcher.unregisterReceiver(this);mRegistered = false;}}
​@Overridepublic void onReceive(Context context, Intent intent) {mDialog.dismiss();}
}

这就是一个简单的广播接收器,收到广播的时候dialog取消,BluetoothDialog继承SystemUIdialog,只是简单的继承,并没有做什么处理,

public class BluetoothDialog extends SystemUIDialog {
​public BluetoothDialog(Context context) {super(context);
​getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);setShowForAllUsers(true);}
}

KeyboardUI.java文件里面持有这个BluetoothDialog的对象,用于监听蓝牙的响应做出相应的弹框处理,KeyBoardUI.java和之前的类似,start方法如下:

public void start() {mContext = super.mContext;HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND);thread.start();mHandler = new KeyboardHandler(thread.getLooper());//创建mHandler.sendEmptyMessage(MSG_INIT);//发送初始化消息
}

这里面创建了个mHandler,是处理键盘的回调的,实例化的代码如下:

private final class KeyboardHandler extends Handler {public KeyboardHandler(Looper looper) {super(looper, null, true /*async*/);}
​@Overridepublic void handleMessage(Message msg) {switch(msg.what) {//初始化case MSG_INIT: {init();break;}//开机启动回调case MSG_ON_BOOT_COMPLETED: {onBootCompletedInternal();break;}//状态改变回调case MSG_PROCESS_KEYBOARD_STATE: {processKeyboardState();break;}//蓝牙可用不可用的回调case MSG_ENABLE_BLUETOOTH: {boolean enable = msg.arg1 == 1;if (enable) {mLocalBluetoothAdapter.enable();} else {mState = STATE_USER_CANCELLED;}break;}//ble扫描case MSG_BLE_ABORT_SCAN: {int scanAttempt = msg.arg1;bleAbortScanInternal(scanAttempt);break;}//蓝牙状态改变case MSG_ON_BLUETOOTH_STATE_CHANGED: {int bluetoothState = msg.arg1;onBluetoothStateChangedInternal(bluetoothState);break;}//蓝牙设备绑定状态改变case MSG_ON_DEVICE_BOND_STATE_CHANGED: {CachedBluetoothDevice d = (CachedBluetoothDevice)msg.obj;int bondState = msg.arg1;onDeviceBondStateChangedInternal(d, bondState);break;}//添加蓝牙设备case MSG_ON_BLUETOOTH_DEVICE_ADDED: {BluetoothDevice d = (BluetoothDevice)msg.obj;CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(d);onDeviceAddedInternal(cachedDevice);break;
​}//ble扫描失败case MSG_ON_BLE_SCAN_FAILED: {onBleScanFailedInternal();break;}//显示错误case MSG_SHOW_ERROR: {Pair<Context, String> p = (Pair<Context, String>) msg.obj;onShowErrorInternal(p.first, p.second, msg.arg1);}}}
}

mHandler创建完成之后,发送了一个初始化的message,走进init方法,init代码如下:

// Shoud only be called on the handler thread
private void init() {Context context = mContext;mKeyboardName =context.getString(com.android.internal.R.string.config_packagedKeyboardName);if (TextUtils.isEmpty(mKeyboardName)) {if (DEBUG) {Slog.d(TAG, "No packaged keyboard name given.");}return;}
​LocalBluetoothManager bluetoothManager = Dependency.get(LocalBluetoothManager.class);if (bluetoothManager == null)  {if (DEBUG) {Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance");}return;}mEnabled = true;mCachedDeviceManager = bluetoothManager.getCachedDeviceManager();mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter();mProfileManager = bluetoothManager.getProfileManager();bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler());BluetoothUtils.setErrorListener(new BluetoothErrorListener());
​InputManager im = context.getSystemService(InputManager.class);im.registerOnTabletModeChangedListener(this, mHandler);mInTabletMode = im.isInTabletMode();
​processKeyboardState();mUIHandler = new KeyboardUIHandler();//又创建了个更新键盘UI的handler
}

init方法主要拿缓冲设备,蓝牙,输入信息,然后又创建了个mUIHandler用于更新UI相关内容,mUiHandler的代码如下:

private final class KeyboardUIHandler extends Handler {public KeyboardUIHandler() {super(Looper.getMainLooper(), null, true /*async*/);}@Overridepublic void handleMessage(Message msg) {switch(msg.what) {case MSG_SHOW_BLUETOOTH_DIALOG: {if (mDialog != null) {// Don't show another dialog if one is already presentbreak;}DialogInterface.OnClickListener clickListener =new BluetoothDialogClickListener();DialogInterface.OnDismissListener dismissListener =new BluetoothDialogDismissListener();mDialog = new BluetoothDialog(mContext);//dialog是之前的蓝牙dialogmDialog.setTitle(R.string.enable_bluetooth_title);mDialog.setMessage(R.string.enable_bluetooth_message);mDialog.setPositiveButton(R.string.enable_bluetooth_confirmation_ok, clickListener);mDialog.setNegativeButton(android.R.string.cancel, clickListener);mDialog.setOnDismissListener(dismissListener);mDialog.show();break;}case MSG_DISMISS_BLUETOOTH_DIALOG: {if (mDialog != null) {mDialog.dismiss();}break;}}}
}

这个比较简单,就是做弹框,取消弹框的处理,整体来看这个KeyboardUI里面有很多的回调,大概列举下名字如下:

processKeyboardState()
onBootCompletedInternal()
showBluetoothDialog()
getPairedKeyboard()
getDiscoveredKeyboard()
getCachedBluetoothDevice()
startScanning()
stopScanning()
bleAbortScanInternal()
onDeviceAddedInternal()
onBluetoothStateChangedInternal()
onBleScanFailedInternal()
onShowErrorInternal()

整体来看这些名字全部都和蓝牙有关系。

2.9PipUI图片窗口中的图片

PipUI是android系统处理画中画的一个SystemUI,构造方法中传入commonQueue的对象和mPipManager,mPipManager用于处理画中画的操作,PipUI重写的start方法如下:

@Override
public void start() {PackageManager pm = mContext.getPackageManager();boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);if (!supportsPip) {return;}
​// Ensure that we are the primary user's SystemUI.final int processUser = UserManager.get(mContext).getUserHandle();if (processUser != UserHandle.USER_SYSTEM) {throw new IllegalStateException("Non-primary Pip component not currently supported.");}
​mCommandQueue.addCallback(this);
}

拿到packageManager判断是否是主用户的SystemUI,之后把当前对象添加到mCommandQueue的回调中,这个类回调只做了一个处理,

@Override
public void showPictureInPictureMenu() {mPipManager.showPictureInPictureMenu();
}
​
public void expandPip() {mPipManager.expandPip();
}

显示画中画的菜单,下面的收起画中画的操作,都是通过pipManager做的操作的。

2.10ShortcutKeyDispatcher快捷分发器

ShortcutKeyDispatcher这个类主要是提供快捷功能的,构造方法如下:

@Inject
public ShortcutKeyDispatcher(Context context, Divider divider, Recents recents) {super(context);mDivider = divider;mRecents = recents;
}

传入分屏对象,最近使用的app进程对象,在start方法中注册内容:

@Override
public void start() {registerShortcutKey(SC_DOCK_LEFT);registerShortcutKey(SC_DOCK_RIGHT);
}

注册方法如下:

/*** Registers a shortcut key to window manager.* @param shortcutCode packed representation of shortcut key code and meta information
*/
public void registerShortcutKey(long shortcutCode) {try {mWindowManagerService.registerShortcutKey(shortcutCode, mShortcutKeyServiceProxy);} catch (RemoteException e) {// Do nothing}
}

通过wms注册快捷进入的功能,当点击的时候触发下面的方法:

@Override
public void onShortcutKeyPressed(long shortcutCode) {int orientation = mContext.getResources().getConfiguration().orientation;if ((shortcutCode == SC_DOCK_LEFT || shortcutCode == SC_DOCK_RIGHT)&& orientation == Configuration.ORIENTATION_LANDSCAPE) {handleDockKey(shortcutCode);}
}

继续调用handleDockKey方法:

private void handleDockKey(long shortcutCode) {if (mDivider == null || !mDivider.isDividerVisible()) {// Split the screenmRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT: SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);} else {// If there is already a docked window, we respond by resizing the docking pane.DividerView dividerView = mDivider.getView();DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();int dividerPosition = dividerView.getCurrentPosition();DividerSnapAlgorithm.SnapTarget currentTarget =snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)? snapAlgorithm.getPreviousTarget(currentTarget): snapAlgorithm.getNextTarget(currentTarget);dividerView.startDragging(true /* animate */, false /* touching */);dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,true /* logMetrics */);}
}

判断分屏状态,如果当前分屏是空,或者分屏不可见的情况下,做分屏处理,其他的情况重新测量这个view做处理。

2.11VendorServices供应商服务

这个里面是空实现,是为了给供应商留空白的位置,暂时什么都没有。

2.12SliceBroadcastRelayHandler允许打开设置App

SliceBroadcastRelayHandler.java,这个类是通过广播实现的,注释里面的意思是允许设置注册某些广播来启动设置的app,start方法如下:

@Override
public void start() {if (DEBUG) Log.d(TAG, "Start");IntentFilter filter = new IntentFilter(SliceBroadcastRelay.ACTION_REGISTER);filter.addAction(SliceBroadcastRelay.ACTION_UNREGISTER);mBroadcastDispatcher.registerReceiver(mReceiver, filter);
}

这里面同光广播分发器注册了一个receiver,其中receiver的handleIntent()代码如下:

// This does not use BroadcastDispatcher as the filter may have schemas or mime types.
@VisibleForTesting
void handleIntent(Intent intent) {if (SliceBroadcastRelay.ACTION_REGISTER.equals(intent.getAction())) {Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);ComponentName receiverClass =intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER);IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER);if (DEBUG) Log.d(TAG, "Register " + uri + " " + receiverClass + " " + filter);getOrCreateRelay(uri).register(mContext, receiverClass, filter);} else if (SliceBroadcastRelay.ACTION_UNREGISTER.equals(intent.getAction())) {Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);if (DEBUG) Log.d(TAG, "Unregister " + uri);BroadcastRelay relay = getAndRemoveRelay(uri);if (relay != null) {relay.unregister(mContext);}}
}

里面做了两个处理,第一个是注册,第二个是取消注册,通过uri从mRelays里面拿到一个BroadcastRelay类型的对象,做注册和取消注册注册的操作,BroadcastRelay也是一个广播其中的onReceive的代码如下:

@Override
public void onReceive(Context context, Intent intent) {intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);for (ComponentName receiver : mReceivers) {intent.setComponent(receiver);intent.putExtra(SliceBroadcastRelay.EXTRA_URI, mUri.toString());if (DEBUG) Log.d(TAG, "Forwarding " + receiver + " " + intent + " " + mUserId);context.sendBroadcastAsUser(intent, mUserId);}
}

这里面做的操作比较简单,就是把这个广播全部都发送出去,多添加了一个EXTRA_URI的标记。

2.13SizeCompatModeActivityController允许重新启动activity的控制器

SizeCompatModeActivityController这个类主要是做重新启动activity的作用,构造方法中的处理如下:

@VisibleForTesting
@Inject
SizeCompatModeActivityController(Context context, ActivityManagerWrapper am,CommandQueue commandQueue) {super(context);mCommandQueue = commandQueue;am.registerTaskStackListener(new TaskStackChangeListener() {@Overridepublic void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {// Note the callback already runs on main thread.updateRestartButton(displayId, activityToken);}});
}

传入commandQueue,am中注册重新启动activity的按钮,任务栈发生改变的监听,updateRestartButton会更新一个Button或者创建一个button,这个button的onClick事件如下:

@Override
public void onClick(View v) {try {ActivityTaskManager.getService().restartActivityProcessIfVisible(mLastActivityToken);} catch (RemoteException e) {Log.w(TAG, "Unable to restart activity", e);}
}

拿到taskManager服务重启activity,入参是mLastActivityToken,这个参数就是上面需要变动的activityToken,SizeCompatModeActivityController的start方法如下:

@Override
public void start() {mCommandQueue.addCallback(this);
}

跟之前的操作类似,添加自身作为回调,重写的方法有两个:

@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,boolean showImeSwitcher) {RestartActivityButton button = mActiveButtons.get(displayId);if (button == null) {return;}boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;int newVisibility = imeShown ? View.GONE : View.VISIBLE;// Hide the button when input method is showing.if (button.getVisibility() != newVisibility) {button.setVisibility(newVisibility);}
}
​
@Override
public void onDisplayRemoved(int displayId) {mDisplayContextCache.remove(displayId);removeRestartButton(displayId);
}

这两个方法都是在控制button显示或者不显示。

2.14InstantAppNotifier即时应用程序通知

InstantAppNotifier是即时应用程序的通知,即时应用程序的概念和微信小程序类似, “快应用”标准是九家手机厂商基于硬件平台共同推出的新型应用生态,快应用使用前端技术栈开发,原生渲染,同时具备H5页面和原生应用的双重优点。用户无需下载安装,即点即用,享受原生应用的性能体验。

  1. 快应用是基于手机硬件平台的新型应用形态,标准是由主流手机厂商组成的快应用联盟联合制定。

  2. 快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台,以平台化的生态模式对个人开发者和企业开发者全品类开放。

  3. 快应用具备传统APP完整的应用体验,无需安装、即点即用。

简单的说就是允许用户免安装直接打开网页做对应的用户操作,打开android应用商店搜索“快应用”能够直接搜索到, 主要是针对小型app,大型app在这个快应用中心并没有,本身针对的体量就比较小,快应用开发参考文档地址:

[快应用开发参考文档]  https://www.w3cschool.cn/quickapp/quickapp-6ams2o7m.html no

这个类的start方法中对键盘,分屏,通知做了初始化,代码如下:

@Override
public void start() {mKeyguardStateController = Dependency.get(KeyguardStateController.class);
​// listen for user / profile change.try {ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG);} catch (RemoteException e) {// Ignore}
​mCommandQueue.addCallback(this);mKeyguardStateController.addCallback(this);
​mDivider.registerInSplitScreenListener(exists -> {mDockedStackExists = exists;updateForegroundInstantApps();});
​// Clear out all old notifications on startup (only present in the case where sysui dies)NotificationManager noMan = mContext.getSystemService(NotificationManager.class);for (StatusBarNotification notification : noMan.getActiveNotifications()) {if (notification.getId() == SystemMessage.NOTE_INSTANT_APPS) {noMan.cancel(notification.getTag(), notification.getId());}}
}

后面调用通知的方法名是:

updateForegroundInstantApps//(通知前台快应用的app)
checkAndPostForPrimaryScreen//(检查通知主屏幕)
checkAndPostForStack//(检查并通知栈)

2.15ToastUI弹窗提示

ToastUI是系统的弹窗提示,继承自SystemUI实现CommandQueue.Callbacks接口,调用start方法后,把当前对象的回调传给mCommandQueue,代码如下:

@VisibleForTesting
ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,@Nullable IAccessibilityManager accessibilityManager) {super(context);mCommandQueue = commandQueue;mNotificationManager = notificationManager;mAccessibilityManager = accessibilityManager;Resources resources = mContext.getResources();mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
}
​
@Override
public void start() {mCommandQueue.addCallback(this);
}
​
@Override
@MainThread
public void showToast(int uid, String packageName, IBinder token, CharSequence text,IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {if (mPresenter != null) {hideCurrentToast();}Context context = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0);View view = ToastPresenter.getTextToastView(context, text);mCallback = callback;mPresenter = new ToastPresenter(context, mAccessibilityManager, mNotificationManager,packageName);mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
}
​
@Override
@MainThread
public void hideToast(String packageName, IBinder token) {if (mPresenter == null || !Objects.equals(mPresenter.getPackageName(), packageName)|| !Objects.equals(mPresenter.getToken(), token)) {Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);return;}hideCurrentToast();
}
​
@MainThread
private void hideCurrentToast() {mPresenter.hide(mCallback);mPresenter = null;
}

初始化时赋值mCommandQueue,这个队列是实现消息回传的一个类,ToastUI中的showToast和hideToast都加的有主线程的注解,这个充当Model层和Presenter层的中间层,数据源发生变化,需要调用showToast时,通过new Presenter构造prensenter后调用presenter层的show方法,也是MVP的架构,presenter的主要代码如下:

构造方法:

public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,INotificationManager notificationManager, String packageName) {mContext = context;mResources = context.getResources();mWindowManager = context.getSystemService(WindowManager.class);mNotificationManager = notificationManager;mPackageName = packageName;
​// We obtain AccessibilityManager manually via its constructor instead of using method// AccessibilityManager.getInstance() for 2 reasons://   1. We want to be able to inject IAccessibilityManager in tests to verify behavior.//   2. getInstance() caches the instance for the process even if we pass a different//      context to it. This is problematic for multi-user because callers can pass a context//      created via Context.createContextAsUser().mAccessibilityManager = new AccessibilityManager(context, accessibilityManager,context.getUserId());
​mParams = createLayoutParams();
}

show和hide方法:

/*** Shows the toast in {@code view} with the parameters passed and callback {@code callback}.*/
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,int xOffset, int yOffset, float horizontalMargin, float verticalMargin,@Nullable ITransientNotificationCallback callback) {checkState(mView == null, "Only one toast at a time is allowed, call hide() first.");mView = view;mToken = token;
​adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,horizontalMargin, verticalMargin);if (mView.getParent() != null) {mWindowManager.removeView(mView);}try {mWindowManager.addView(mView, mParams);} catch (WindowManager.BadTokenException e) {// Since the notification manager service cancels the token right after it notifies us// to cancel the toast there is an inherent race and we may attempt to add a window// after the token has been invalidated. Let us hedge against that.Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);return;}trySendAccessibilityEvent(mView, mPackageName);if (callback != null) {try {callback.onToastShown();} catch (RemoteException e) {Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);}}
}
​
/*** Hides toast that was shown using {@link #show(View, IBinder, IBinder, int,* int, int, int, float, float, ITransientNotificationCallback)}.** <p>This method has to be called on the same thread on which {@link #show(View, IBinder,* IBinder, int, int, int, int, float, float, ITransientNotificationCallback)} was called.*/
public void hide(@Nullable ITransientNotificationCallback callback) {checkState(mView != null, "No toast to hide.");
​if (mView.getParent() != null) {mWindowManager.removeViewImmediate(mView);}try {mNotificationManager.finishToken(mPackageName, mToken);} catch (RemoteException e) {Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);}if (callback != null) {try {callback.onToastHidden();} catch (RemoteException e) {Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);}}mView = null;mToken = null;
}

presenter层直接通过windowManager的addView和removeView方法更新界面。

结论:看不懂没关系,兄弟我看不懂的代码多了去了,不差这一点。

SystemUI介绍相关推荐

  1. android 6.0 SystemUI源码分析(1)-SystemUI介绍

    1. SystemUI介绍 SystemUI是一个系统应用,主要功能有: 1)状态栏信息显示,比如电池,wifi信号,3G/4G等icon显示 2)通知面板,比如系统消息,第三方应用消息,都是在通知面 ...

  2. Android SystemUI 架构详解

    Android SystemUI 架构详解 本文描述Android系统中一个核心应用SystemUI,详细赘述SystemUI中几大模块功能的实现过程.由于作者水平有限,如发现本文中错误的地方,欢迎指 ...

  3. SystemUI架构分析

    SystemUI架构分析 SystemUI架构分析 前言 1SystemUI介绍 1SystemUI摘要 2什么是SystemUI 2SystemUI的启动过程 3SystemUI的SERVICES ...

  4. Android 9.0系统源码_SystemUI(一)SystemUI的启动流程

    一.SystemUI 介绍 1.初步认识SystemUI Android 的 SystemUI 其实就是 Android 的系统界面,它包括了界面上方的状态栏 status bar,下方的导航栏Nav ...

  5. Android 系统(58)---Android 系统 UI - SystemUI之功能介绍和UI布局实现

    Android 系统 UI - SystemUI之功能介绍和UI布局实现 前言 Android ROM开发过程中,难免会涉及到对SystemUI的修改,之前做过一些这方面的工作,现在整理下,准备按照如 ...

  6. android uid systemui,SystemUI-功能介绍

    1. SystemUI与普通应用的区别 SystemUI 普通应用 UID 1000 应用安装时分配,>10000 编译方式 基于Android源码编译 基于Android SDK编译 安装方式 ...

  7. Android 系统 UI - SystemUI之功能介绍和UI布局实现

    前言 Android ROM开发过程中,难免会涉及到对SystemUI的修改,之前做过一些这方面的工作,现在整理下,准备按照如下章节介绍SystemUI.借此对SystemUI做下整体性回顾.  -S ...

  8. 风暴数码论坛教程--apk和odex的介绍和合并

    一.apk和odex的介绍和合并 (一)APK介绍 APK是Android Package的缩写,即Android安装包.APK是类似Symbian Sis或Sisx的文件格式.通过将APK文件直接传 ...

  9. Android 7.0 SystemUI 之启动和状态栏和导航栏简介

    Android 7.0 SystemUI 之启动和状态栏和导航栏简介 一.SystemUI 是什么 首先SystemUI 是一个系统应用,apk路径位于/system/priv-app 源码路径位于: ...

最新文章

  1. Spring-redis基础配置
  2. linux中正则表达式、find、xargs、grep以及sed等命令的用法
  3. Redis AOF带来的问题
  4. 小程序app is not defined
  5. Leetcode--837. 新21点(java)
  6. jvm原理及性能调优系列(jvm调优)
  7. Python批量整理文件名小案例(附公众号第一批赠书活动中奖名单)
  8. Python菜鸟入门:day10模块介绍
  9. Effective_STL 学习笔记(三) 使容器里对象的拷贝操作轻量而正确
  10. 测量学用C语言编程求子午线弧长,GPS数据解析 数据拆分 坐标转换 显示线路图源代码...
  11. 陪集编码(Coset coding)
  12. 一步步学习SPD2010--第八章节--理解工作流(8)--使用Visio映射工作流
  13. LINUX下截图快捷方式
  14. android 分享到新浪微博,Android APP集成新浪微博分享功能
  15. 申请软件著作权步骤如下
  16. set_set_switching_activity
  17. python怎么把字体变大_Pycharm 字体大小调整设置的方法实现
  18. C语言求CHO的相对分子质量
  19. 微信“公众平台测试账号”接口调试指南
  20. 数学建模方法总结(matlab)

热门文章

  1. windows10安装绿色版Tomcat7
  2. [绍棠] Vue六种传值方式
  3. 使用C++代码打造:史上最恶心的“推箱子”游戏,你敢再坑爹点吗?
  4. 关于钉钉打卡的另一种实现思路
  5. 昆明-大理-丽江-泸沽湖最新自驾游记
  6. 5个基本的统计学概念,你知道多少?
  7. Unity由于找不到MSVCP120.dll,无法继续执行代码
  8. Oracle11g和oracle10g之间的导入/导出
  9. JOS学习笔记(七)
  10. js选择器获取元素的value值,如何判断为空