谷歌对Android4.2的版本做了一些改动,突出的改动就是锁屏可以添加widget,即锁屏widget,这个是谷歌的一个大的改动,先来说说android4.2做了哪些改动?
(1)Lock screen widgets如图:
(2)梦话模式的添加
简单介绍如下:白日梦是一个互动屏幕保护程序模式,当用户的设备开始停靠或充电。在这种模式下,系统将启动一枕黄粱 - 远程安装的应用程序提供内容服务 - 设备的屏幕保护程序。用户可以设置应用程序启用白日梦,然后选择显示遐想。
(3)更多显示的支持
(4)Native RTL support从右向左支持,例如印度语,就是这种显示格式。
等等,更多特性请参考官网:http://developer.android.com/about/versions/jelly-bean.html#42-external-display
好了,言归正传,我们来说说Android4.2锁屏的流程:咱们一步一步来说:
Step1:先看第一次开机的加载锁屏的过程,通过PhoneWindowManager.java这个类的systemReady()这个方法,当系统开机准备好的情况下会调用这个方法,如下:
public void systemReady() {
if (mKeyguardMediator != null) {
// tell the keyguard
mKeyguardMediator.onSystemReady();
}
synchronized (mLock) {
updateOrientationListenerLp();
mSystemReady = true;
mHandler.post(new Runnable() {
public void run() {
updateSettings();
}
});
}
}
Step2:看注释就知道下一步该干什么了,告诉锁屏的管理者,我准备好了,该你来控制加载锁屏界面了。接着调用到了KeyguardViewMediator.java这个类的onSystemReady()方法,如下:
/**
* Let us know that the system is ready after startup.
*/
public void onSystemReady() {
mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
synchronized (this) {
if (DEBUG) Log.d(TAG, "onSystemReady");
mSystemReady = true;
mUpdateMonitor.registerCallback(mUpdateCallback);
// Suppress biometric unlock right after boot until things have settled if it is the
// selected security method, otherwise unsuppress it. It must be unsuppressed if it is
// not the selected security method for the following reason: if the user starts
// without a screen lock selected, the biometric unlock would be suppressed the first
// time they try to use it.
//
// Note that the biometric unlock will still not show if it is not the selected method.
// Calling setAlternateUnlockEnabled(true) simply says don't suppress it if it is the
// selected method.
if (mLockPatternUtils.usingBiometricWeak()
&& mLockPatternUtils.isBiometricWeakInstalled()
|| mLockPatternUtils.usingVoiceWeak()
&& FeatureOption.MTK_VOICE_UNLOCK_SUPPORT) {
if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot");
mUpdateMonitor.setAlternateUnlockEnabled(false);
} else {
mUpdateMonitor.setAlternateUnlockEnabled(true);
}
/// M: power-off alarm @{
if (!KeyguardUpdateMonitor.isAlarmBoot()) {
doKeyguardLocked();
}
/// @}
}
// Most services aren't available until the system reaches the ready state, so we
// send it here when the device first boots.
maybeSendUserPresentBroadcast();
}
Step3:接着由doKeyguardLocked()这个方法来做启动锁屏界面的预处理,来看看这个方法都做了什么:
private void doKeyguardLocked() {
doKeyguardLocked(null);
}
/**
* Enable the keyguard if the settings are appropriate.
*/
private void doKeyguardLocked(Bundle options) {
// if another app is disabling us, don't show
if (!mExternallyEnabled || KeyguardUpdateMonitor.isAlarmBoot()) {
if (DEBUG) KeyguardUtils.xlogD(TAG, "doKeyguard: not showing because externally disabled");
// note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
// for an occasional ugly flicker in this situation:
// 1) receive a call with the screen on (no keyguard) or make a call
// 2) screen times out
// 3) user hits key to turn screen back on
// instead, we reenable the keyguard when we know the screen is off and the call
// ends (see the broadcast receiver below)
// TODO: clean this up when we have better support at the window manager level
// for apps that wish to be on top of the keyguard
return;
}
// if the keyguard is already showing, don't bother
if (mKeyguardViewManager.isShowing()) {
if (DEBUG) KeyguardUtils.xlogD(TAG, "doKeyguard: not showing because it is already showing");
return;
}
// if the setup wizard hasn't run yet, don't show
if (DEBUG) KeyguardUtils.xlogD(TAG, "doKeyguard: get keyguard.no_require_sim property before");
final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim",
false);
if (DEBUG) KeyguardUtils.xlogD(TAG, "doKeyguard: get keyguard.no_require_sim property after");
final boolean provisioned = mUpdateMonitor.isDeviceProvisioned();
final IccCardConstants.State state = mUpdateMonitor.getSimState();
boolean lockedOrMissing = false;
/// M: Support GeminiPlus
for (int i = PhoneConstants.GEMINI_SIM_1; i <= KeyguardUtils.getMaxSimId(); i++) {
lockedOrMissing = (lockedOrMissing || isLockedOrMissingGemini(i, requireSim));
if (lockedOrMissing) {
break;
}
}
if (DEBUG) KeyguardUtils.xlogD(TAG, "doKeyguard: get sim state after");
/// M: MTK MOTA UPDATE when on ics2 keygaurd set none,update to JB,the keyguard will show LockScreen.
/// MTK MOTA UPDATE when the phone first boot,check the settingDB mirged or not ,because mota update,
/// the settingdb migrate slow than keygaurd(timing sequence problem) @{
boolean keyguardDisable = false;
/*************************************TODO
boolean motaUpdateFirst = true;//mLockPatternUtils.isDbMigrated();
if (motaUpdateFirst) {
/// DB mogi done
keyguardDisable = mLockPatternUtils.isLockScreenDisabled();
} else {
/// DB not mogi
final ContentResolver cr = mContext.getContentResolver();
String value = Settings.Secure.getString(cr, "lockscreen.disabled");
boolean booleanValue = false;
if( null!=value ){
booleanValue = value.equals("1") ? true :false;
}
keyguardDisable = (!mLockPatternUtils.isSecure()) && booleanValue;
}
/// @}
if (DEBUG) KeyguardUtils.xlogD(TAG, "doKeyguard: keyguardDisable query end");
/// M: Add new condition DM lock is not true
boolean dmLocked = KeyguardUpdateMonitor.getInstance(mContext).dmIsLocked();
KeyguardUtils.xlogD(TAG, "lockedOrMissing is " + lockedOrMissing + ", requireSim=" + requireSim
+ ", provisioned=" + provisioned + ", keyguardisable=" + keyguardDisable + ", dmLocked=" + dmLocked);
if (!lockedOrMissing && !provisioned && !dmLocked) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
+ " and the sim is not locked or missing");
return;
}
/// M: Add a new condition DM lock is not on, or user can still bypass dm lock when Keygaurd is disabled
if (mUserManager.getUsers(true).size() < 2
&& keyguardDisable && !lockedOrMissing && !KeyguardUpdateMonitor.getInstance(mContext).dmIsLocked()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
return;
}
if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
showLocked(options);
}
Step4、来注意最后调用的这个方法showLocked(options),这个方法是启动锁屏关键的方法,来看看:
/**
* Send message to keyguard telling it to show itself
* @see #handleShow()
*/
private void showLocked(Bundle options) {
if (DEBUG) KeyguardUtils.xlogD(TAG, "showLocked");
// ensure we stay awake until we are finished displaying the keyguard
mShowKeyguardWakeLock.acquire();
Message msg = mHandler.obtainMessage(SHOW, options);
mHandler.sendMessage(msg);
}
Step5、这下就通过发送消息来进一步启动锁屏界面,来看看这个mHandler中的SHOW都做了什么:
public void handleMessage(Message msg) {
if (DBG_MESSAGE) KeyguardUtils.xlogD(TAG, "handleMessage enter msg name=" + getMessageString(msg));
switch (msg.what) {
case SHOW:
handleShow((Bundle) msg.obj);
break;
调用
的是handleShow()这个方法:
/**
* Handle message sent by {@link #showLocked}.
* @see #SHOW
*/
private void handleShow(Bundle options) {
synchronized (KeyguardViewMediator.this) {
if (DEBUG) KeyguardUtils.xlogD(TAG, "handleShow enter");
if (!mSystemReady) return;
/// M: if already showing, just return
if (mShowing) return;
mKeyguardViewManager.show(options);
if (DEBUG) KeyguardUtils.xlogD(TAG, "handleShow mKeyguardViewManager Show exit");
mShowing = true;
mKeyguardDonePending = false;
updateActivityLockScreenState();
adjustStatusBarLocked();
userActivity();
try {
ActivityManagerNative.getDefault().closeSystemDialogs("lock");
} catch (RemoteException e) {
}
if (DEBUG) KeyguardUtils.xlogD(TAG, "handleShow query AlarmBoot before");
// Do this at the end to not slow down display of the keyguard.
if (!KeyguardUpdateMonitor.isAlarmBoot()) {
playSounds(true);
} else {
new Handler().postDelayed(new Runnable() {
public void run() {
sendRemoveIPOWinBroadcast();
startAlarm();
}
}, 250);
}
mShowKeyguardWakeLock.release();
if (DEBUG) KeyguardUtils.xlogD(TAG, "handleShow exit");
}
}
/**
* Show the keyguard. Will handle creating and attaching to the view manager
* lazily.
*/
public synchronized void show(Bundle options) {
if (DEBUG) KeyguardUtils.xlogD(TAG, "show(); mKeyguardView=" + mKeyguardView);
boolean enableScreenRotation = shouldEnableScreenRotation();
if (DEBUG) KeyguardUtils.xlogD(TAG, "show() query screen rotation after");
/// M: Incoming Indicator for Keyguard Rotation @{
KeyguardUpdateMonitor.getInstance(mContext).setQueryBaseTime();
/// @}
maybeCreateKeyguardLocked(enableScreenRotation, false, options);
if (DEBUG) KeyguardUtils.xlogD(TAG, "show() maybeCreateKeyguardLocked finish");
maybeEnableScreenRotation(enableScreenRotation);
// Disable common aspects of the system/status/navigation bars that are not appropriate or
// useful on any keyguard screen but can be re-shown by dialogs or SHOW_WHEN_LOCKED
// activities. Other disabled bits are handled by the KeyguardViewMediator talking
// directly to the status bar service.
final int visFlags = View.STATUS_BAR_DISABLE_HOME;
if (DEBUG) KeyguardUtils.xlogD(TAG, "show:setSystemUiVisibility(" + Integer.toHexString(visFlags)+")");
mKeyguardHost.setSystemUiVisibility(visFlags);
mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
mKeyguardHost.setVisibility(View.VISIBLE);
mKeyguardView.show();
mKeyguardView.requestFocus();
if (DEBUG) KeyguardUtils.xlogD(TAG, "show() exit; mKeyguardView=" + mKeyguardView);
}
Step7,、这下终于看到如山真面目了,看里面的方法maybeCreateKeyguardLocked()这个是真正起作用的地方:
private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean force,
Bundle options) {
final boolean isActivity = (mContext instanceof Activity); // for test activity
if (mKeyguardHost != null) {
mKeyguardHost.saveHierarchyState(mStateContainer);
}
if (mKeyguardHost == null) {
if (DEBUG) KeyguardUtils.xlogD(TAG, "keyguard host is null, creating it...");
mKeyguardHost = new ViewManagerHost(mContext);
int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
| WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
| WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
/// M: Modify to support DM lock, hide statusbr when dm lock power on @{
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
if (monitor.dmIsLocked()) { //in the first created
if (DEBUG) KeyguardUtils.xlogD(TAG, "show(); dmIsLocked ");
flags &= ~WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
flags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
} else if (KeyguardUpdateMonitor.isAlarmBoot()) {
if (DEBUG) KeyguardUtils.xlogD(TAG, "show(); AlarmBoot ");
flags &= ~WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
flags &= ~WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
flags |= WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
}
/// M: @}
if (!mNeedsInput) {
flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
if (ActivityManager.isHighEndGfx()) {
flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
final int stretch = ViewGroup.LayoutParams.MATCH_PARENT;
final int type = isActivity ? WindowManager.LayoutParams.TYPE_APPLICATION
: WindowManager.LayoutParams.TYPE_KEYGUARD;
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
if (ActivityManager.isHighEndGfx()) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
lp.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
}
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
if (isActivity) {
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
}
/// M: Poke user activity when operating Keyguard
//lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
lp.setTitle(isActivity ? "KeyguardMock" : "Keyguard");
mWindowLayoutParams = lp;
mViewManager.addView(mKeyguardHost, lp);
}
/// M: If force and keyguardView is not null, we should relase memory hold by old keyguardview
if (force && mKeyguardView != null) {
mKeyguardView.cleanUp();
}
if (force || mKeyguardView == null) {
inflateKeyguardView(options);
mKeyguardView.requestFocus();
}
updateUserActivityTimeoutInWindowLayoutParams();
mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
mKeyguardHost.restoreHierarchyState(mStateContainer);
}
这下通过mViewManager.addView(mKeyguardHost, lp);这个方法真正地把锁屏界面添加到屏幕上,其实这个就是个view,挡在了手机的屏幕的最上方。而这个mKeyguardHost就是锁屏的根。而第一次加载的时候
mKeyguardView为空,调用
inflateKeyguardView(),初始化锁屏的view。
Step8、来看看这个inflateKeyguardView()这个方法都加载了哪个布局:
private void inflateKeyguardView(Bundle options) {
/// M: add for power-off alarm @{
int resId = R.id.keyguard_host_view;
int layoutId = R.layout.keyguard_host_view;
if(KeyguardUpdateMonitor.isAlarmBoot()){
layoutId = com.mediatek.internal.R.layout.power_off_alarm_host_view;
resId = com.mediatek.internal.R.id.keyguard_host_view;
}
/// @}
View v = mKeyguardHost.findViewById(resId);
if (v != null) {
mKeyguardHost.removeView(v);
}
// TODO: Remove once b/7094175 is fixed
if (false) Slog.d(TAG, "inflateKeyguardView: b/7094175 mContext.config="
+ mContext.getResources().getConfiguration());
/// M: Save new orientation
mCreateOrientation = mContext.getResources().getConfiguration().orientation;
final LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(layoutId, mKeyguardHost, true);
mKeyguardView = (KeyguardHostView) view.findViewById(resId);
mKeyguardView.setLockPatternUtils(mLockPatternUtils);
mKeyguardView.setViewMediatorCallback(mViewMediatorCallback);
// HACK
// The keyguard view will have set up window flags in onFinishInflate before we set
// the view mediator callback. Make sure it knows the correct IME state.
if (mViewMediatorCallback != null) {
KeyguardPasswordView kpv = (KeyguardPasswordView) mKeyguardView.findViewById(
R.id.keyguard_password_view);
if (kpv != null) {
mViewMediatorCallback.setNeedsInput(kpv.needsInput());
}
}
/// Extract this block to a single function
updateKeyguardViewFromOptions(options);
}
这个加载了keyguard_host_view这个layout,来看看这个布局是怎么写的:
而这个KeyguardHostView.java就是锁屏的真正的处理的view,该添加什么样的锁屏,例如:PIN,Pattern,PUK,Password等等,都是由它来控制的,最后会调用到getLayoutIdFor()这个方法,来启动那种锁屏界面,如下:
private int getLayoutIdFor(SecurityMode securityMode) {
switch (securityMode) {
case None: return R.layout.keyguard_selector_view;
case Pattern: return R.layout.keyguard_pattern_view;
case PIN: return R.layout.keyguard_pin_view;
case Password: return R.layout.keyguard_password_view;
case Biometric: return R.layout.keyguard_face_unlock_view;
case Account: return R.layout.keyguard_account_view;
/// M: Modify Sim unlock layout @{
//case SimPin: return R.layout.keyguard_sim_pin_view;
//case SimPuk: return R.layout.keyguard_sim_puk_view;
case SimPinPukMe1: return com.mediatek.internal.R.layout.keyguard_sim_pin_puk_view;
case SimPinPukMe2: return com.mediatek.internal.R.layout.keyguard_sim_pin_puk_view;
/// M: Support GeminiPlus
case SimPinPukMe3: return com.mediatek.internal.R.layout.keyguard_sim_pin_puk_view;
case SimPinPukMe4: return com.mediatek.internal.R.layout.keyguard_sim_pin_puk_view;
/// @}
/// M: power-off alarm @{
case AlarmBoot: return com.mediatek.internal.R.layout.power_off_alarm_view;
/// @}
///M: add voice unlock view layout
case Voice: return R.layout.zz_keyguard_voice_unlock_view;
default:
return 0;
}
}
到这,锁屏已经初始化完了,要想下面接着分析,估计大家应该都能分析过去了;
特别说明:
1、加载锁屏widget的地方在KeyguardHostView.java的onFinishInflate()中,调用的addDefaultWidget()这个方法中添加了单click事件,最后调用到KeyguardActivityLauncher.java的launcherWidgetPicker()这个方法;
2、要想你写的widget能被锁屏widget过滤出来,只需要在wdget的xml中添加一个属性即可:
android:widgetCategory="home_screen|keyguard",这样你写的桌面widget,也能在锁屏wiget过滤出来,具体布局需要你微调下;
添加一张图,