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 proxies
BinderInternal.nSetBinderProxyCountEnabled(true);
BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
BinderInternal.setBinderProxyCountCallback(
new BinderInternal.BinderProxyLimitListener() {
@Override
public 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 long
? ? ? ? ? ?ti = 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时使用
? ?@Override
? ?public 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的组合,其中比较重要的地方,
<LinearLayout
? ? ? ? ? ? ? ?android: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 change
? ? ? ?private boolean tracking; ?// tracking slider touch
? ? ? ?private int requestedLevel = -1; ?// pending user-requested level via progress changed
? ? ? ?private int iconRes;
? ? ? ?private int iconMuteRes;
? ? ? ?private boolean important;
? ? ? ?private boolean defaultStream;
? ? ? ?private ColorStateList cachedTint;
? ? ? ?private int iconState; ?// from Events
? ? ? ?private ObjectAnimator anim; ?// slider progress animation for non-touch-related updates
? ? ? ?private 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() {
? ? ? ?@Override
? ? ? ?public void onUnlockedChanged() {
?
? ? ? }
?
? ? ? ?@Override
? ? ? ?public void onKeyguardShowingChanged() {
? ? ? ? ? ?if (!isSplitActive() || mView == null) {
? ? ? ? ? ? ? ?return;
? ? ? ? ? }
? ? ? ? ? ?mView.setHidden(mKeyguardStateController.isShowing());
? ? ? ? ? ?if (!mKeyguardStateController.isShowing()) {
? ? ? ? ? ? ? ?mImePositionProcessor.updateAdjustForIme();
? ? ? ? ? }
? ? ? }
?
? ? ? ?@Override
? ? ? ?public 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 place
? ?final 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() {
? ?@Override
? ?public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
? ? ? ?onVolumeStateChangedInternal(vol);
? }
?
? ?@Override
? ?public 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);
? ? ? }
? }
?
? ?@Override
? ?public void onVolumeForgotten(String fsUuid) {
? ? ? ?// Stop annoying the user
? ? ? ?mNotificationManager.cancelAsUser(fsUuid, SystemMessage.NOTE_STORAGE_PRIVATE,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?UserHandle.ALL);
? }
?
? ?@Override
? ?public void onDiskScanned(DiskInfo disk, int volumeCount) {
? ? ? ?onDiskScannedInternal(disk, volumeCount);
? }
?
? ?@Override
? ?public 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) {
? ? ? ?@Override
? ? ? ?public 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) {
? ? ? ? ? ?@Override
? ? ? ? ? ?public 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) {
? ? ? ? ? ?@Override
? ? ? ? ? ?public 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() {
? ?@Override
? ?public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
? ? ? ?throws RemoteException {
? ? ? ?playWithVolumeShaping(token, uri, aa, volume, looping, null);
? }
? ?@Override
? ?public 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();
? }
?
? ?@Override
? ?public 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();
? ? ? }
? }
?
? ?@Override
? ?public 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;
? ? ? }
? }
?
? ?@Override
? ?public 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()
? }
?
? ?@Override
? ?public 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);
? }
?
? ?@Override
? ?public 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();
? }
?
? ?@Override
? ?public String getTitle(Uri uri) {
? ? ? ?final UserHandle user = Binder.getCallingUserHandle();
? ? ? ?return Ringtone.getTitle(getContextForUser(user), uri,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? false /*followSettingsUri*/, false /*allowRemote*/);
? }
?
? ?@Override
? ?public 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;
? ? ? }
? }
?
? ?@Override
? ?public 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*/);
? }
?
? ?@Override
? ?public 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*/);
? }
? ?@Override
? ?public void handleMessage(Message msg) {
? ? ? ?switch(msg.what) {
? ? ? ? ? ?case MSG_SHOW_BLUETOOTH_DIALOG: {
? ? ? ? ? ? ? ?if (mDialog != null) {
? ? ? ? ? ? ? ? ? ?// Don't show another dialog if one is already present
? ? ? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?DialogInterface.OnClickListener clickListener =
? ? ? ? ? ? ? ? ? ?new BluetoothDialogClickListener();
? ? ? ? ? ? ? ?DialogInterface.OnDismissListener dismissListener =
? ? ? ? ? ? ? ? ? ?new BluetoothDialogDismissListener();
? ? ? ? ? ? ? ?mDialog = new BluetoothDialog(mContext);//dialog是之前的蓝牙dialog
? ? ? ? ? ? ? ?mDialog.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 screen
? ? ? ?mRecents.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() {
? ? ? ?@Override
? ? ? ?public 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页面和原生应用的双重优点。用户无需下载安装,即点即用,享受原生应用的性能体验。
-
快应用是基于手机硬件平台的新型应用形态,标准是由主流手机厂商组成的快应用联盟联合制定。 -
快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台,以平台化的生态模式对个人开发者和企业开发者全品类开放。 -
快应用具备传统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方法更新界面。
结论:看不懂没关系,兄弟我看不懂的代码多了去了,不差这一点。
|