Customize a beautiful and practical lock screen APP, if you can win the user’s recognition, replace the system’s own lock screen, is absolutely a large daily entry. This time just sums up the difficulties in the development of lock screen APP on Android platform recently investigated.

One, foreword

The basic principle of the lock screen is simple. Monitor the system’s on-screen broadcast and display the lock screen when the screen is on. Users can unlock the lock screen only by performing a series of actions on the lock screen. Some phones will begin the process of the lock screen interface card, so can clearly see the bright screen after the start of the lock screen interface have delay, so you can also choose to monitor system screen broadcast, turn off the screen when will ready the lock screen interface, directly on screen display (screen after your app will be easier to kill, that attention should be paid to do keep alive).

Note that SCREEN_ON/SCREEN_OFF can only be monitored dynamically for on-screen and off-screen broadcasts, so you need to open another Service to register this Service.

I won’t go into the basic implementation details, but this article will cover only a few of the difficulties encountered.

Two, lock screen implementation difficulties

1. Mask the Home button

Since it is a lock screen interface, of course, the lock screen can only be unlocked through some sliding or input actions on the interface, not simply by pressing the Home button. Since 4.0, Home is directly responded by the system in the framework layer, forcibly back to the desktop, third-party applications can no longer use the Activity. OnKeyDown method to listen to and intercept the Home button, although it still symbolically retains the Home button KeyCode for forward compatibility. But pressing the Home button does not call back this method.

Is there any other way to listen on the Home button besides onKeyDown? Yes. ACTION_CLOSE_SYSTEM_DIALOGS broadcast action_system_dialogs broadcast ACTION_CLOSE_SYSTEM_DIALOGS broadcast Action_system_dialogs After the home button is pressed, Reason is Homekey. After the recent task button is pressed, Reason is RecentApps.

This is certainly not the final plan, as some Samsung ROMs do not have this broadcast. You can listen to the home button, but there is no way to intercept the home button. I tried it. After the home button is pressed, startActivity will delay for about 3 seconds. It should be that Google has thought of this for a long time and made such a delay plan.

Direct intercept won’t work. Think of another way. Press the Home button to send the system back to the Launcher. If our locked screen Activity is a Launcher itself, press the Home button to return to our locked screen Activity and prevent it from closing the locked screen Activity.

How to declare your Activity as a Launcher by adding intent-filter to your Activity:


    
    
    
    Copy the code

That way, your newly installed app will be an app that functions as a launcher, so the first time you press the Home button, you’ll be prompted to choose which launcher to enter. Choose your own Activity, and we’ll take over the Home button.

However, there is an obvious problem. If you do not press the Home button on our lock screen, you will also enter the lock screen Activity. Of course, the solution is also simple, when we press Home to enter the lock screen Activity onCreate to make a judgment, if the previous foreground Activity is a lock screen Activity, then do not need to deal with the Home button, if it is not a lock screen Activity, then close the lock screen Activity. Jump to the user’s real desktop launcher. Which is the real desktop launcher? We can find it like this:

List pkgNamesT = new ArrayList(); List actNamesT = new ArrayList(); The List resolveInfos = context. GetPackageManager (). QueryIntentActivities (intent, PackageManager. MATCH_DEFAULT_ONLY); for (int i = 0; i < resolveInfos.size(); i++) { String string = resolveInfos.get(i).activityInfo.packageName; if (! String.equals (context.getPackagename ())) {pkgNamesT. Add (string); string = resolveInfos.get(i).activityInfo.name; actNamesT.add(string); }}Copy the code

If you only have one launcher, jump to it:

ComponentName componentName = new ComponentName(pkgName, actName); 
Intent intent = new Intent(); 
intent.setComponent(componentName); 
context.startActivity(intent); 
((Activity) context).finish();Copy the code

If a mobile phone is equipped with multiple launcher apps (such as 360 desktop apps), it will be a bit troublesome. It needs to display a list for users to choose which launcher to use, which may make users feel a little confused in terms of product form. Now, if you press the Home button in another APP, it will jump to our lock screen Activity and jump to the actual launcher. There may be a scene where the Activity flashes, affecting the user experience. The best way to do this is to create another Activity that can be used as a Home button to jump to. This Activity is transparent so that it is not perceived by the user. In this way, the product form becomes: Press the Home button in the locked screen Activity, jump to the transparent Activity, jump back to the locked screen Activity, equivalent to the Home button is invalid; In other apps, press the Home button to jump to the transparent Activity and jump to the real desktop. Realize transparent Activity, only in the XML declaration android: theme = “@ android: style/theme. Translucent. The NoTitleBar” interface is transparent, in fact have a placeholder in the top of the screen, So remember to finish after the jump, otherwise it will block the interface interaction after the jump. NoDisplay can also make an Activity invisible and non-placeholder, but when I implement it, I find that the NoDisplay Activity cannot be set to the system launcher (it will pop up and ask you to reset it again and again).

2. Realization method of suspension window

Restricted by the fact that the Home button cannot directly intercept, the Activity implementation’s lock screen requires a lot of detour. That’s why some lock screen apps use a hover window that ignores the Home button and doesn’t retreat into the background when the Home button is pressed. So there’s no need to obsess over the home button. The suspension window is managed by WindowManager. The specific implementation is relatively simple, and the author will not repeat the details. It should be noted that the suspension window needs to declare permissions:

Copy the code

Some phone Settings do not grant the application permission to use the hover window by default, so the application should consider instructing the user to authorize the use of the hover window.

In addition, there are some emergency unlocking scenarios, such as incoming calls and alarm processing. For the lock screen interface of the Activity implementation, the system will automatically hide all foreground activities and let the user directly deal with these scenarios. However, the suspension window will cover the scene, so the lock screen interface implemented by the suspension window has to handle the automatic unlock of these special scenes by itself.

3. Disable screen lock

With the lock screen, you need to disable the lock screen of the system to avoid the situation that users need to unlock the system twice. First, we need to know whether the user has set the lock screen. The method is as follows: For THE SDK with API Level 16 and above, you can use the following method to determine whether there is a lock:

((KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE)).isKeyguardSecure()Copy the code

For SDKS with API Level 15 and below, you can use reflection to determine:

try {
    Class clazz = Class.forName("com.android.internal.widget.LockPatternUtils");
    Constructor constructor = clazz.getConstructor(Context.class);
    constructor.setAccessible(true);
    Object utils = constructor.newInstance(this);
    Method method = clazz.getMethod("isSecure");
    return (Boolean) method.invoke(utils);
    }catch (Exception e){
    e.printStackTrace();
    }Copy the code

Ok, I know that the user has set the system lock screen, how to turn off? This method has been suggested before

KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("");
keyguardLock.disableKeyguard();Copy the code

Need to access

Copy the code

However, according to the author’s test, this method can only disable the sliding lock, if the user set a pattern or PIN lock, it can not be cancelled directly. Disabling the password lock or pattern lock is a very dangerous behavior, because of this, Google should not open it to developers, so the current lock screen application to disable the lock, are directly jump to the system lock screen Settings interface, directly guide the user to manually close. You can jump to the user lock screen setting interface by following the code:

Intent in = new Intent(Settings.ACTION_SECURITY_SETTINGS);
startActivity(in);Copy the code

There will also be some compatibility problems. For example, the ROM of 360 mobile phone does not put the function of setting the system lock screen in the security Settings, so there is no place to cancel the system lock screen when opening the interface of security Settings, which is not compatible with many lock screen applications.

3. Difficulties in additional functions

The above functions are directly for the lock screen itself. Lock screen application in addition to itself can have the “lock screen” function, but also should have some other beautiful and practical functions, at least should be as close as possible to the system lock screen style and play, it is convenient to be accepted by users.

1. Obtain notifications

The new Notification should be displayed on the lock screen when it arrives, so we need to listen on the Notification bar. Starting from Android 4.3 (API) 18, Google provides us a NotificationListenerService class, third-party applications can be more convenient to obtain the right to use Notification bar (Notification Access), of course, Such sensitive permissions must be declared by the application itself, and the user must be instructed to manually authorize them. As follows, to establish a NotificationMonitor class inheritance in NotificationListenerService, and declare permissions:


    
        
    
    Copy the code

Then, just as you would instruct the user to turn off the system lock screen, you should instruct the user to authorize the notification bar:

startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));Copy the code

You can check whether the right to use the notification bar has been obtained by using the following code:

private boolean isNotificationListenEnabled(){ String pkgName = getPackageName(); final String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners"); if (! TextUtils.isEmpty(flat)) { final String[] names = flat.split(":"); for (int i = 0; i < names.length; i++) { final ComponentName cn = ComponentName.unflattenFromString(names[i]); if (cn ! = null) { if (TextUtils.equals(pkgName, cn.getPackageName())) { return true; } } } } return false; }Copy the code

Changes to the notification bar are monitored in the NotificationMonitor:

public class NotificationMonitor extends NotificationListenerService { @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent,flags,startId); Override public void onNotificationPosted(StatusBarNotification SBN) { super.onNotificationPosted(sbn); } // New Notification arrives, @override public void onNotificationPosted(StatusBarNotification SBN, RankingMap rankingMap) { super.onNotificationPosted(sbn, rankingMap); } @override public void onNotificationRemoved(StatusBarNotification SBN) { super.onNotificationRemoved(sbn); } //Notification is removed. API 21 added @Override public void onNotificationRemoved(StatusBarNotification SBN, RankingMap rankingMap) { super.onNotificationRemoved(sbn, rankingMap); } // the order of Notification is changed, API 21 new @ Override public void onNotificationRankingUpdate (RankingMap RankingMap) { super.onNotificationRankingUpdate(rankingMap); } // The notification callback can be received only after the Service is bound to the system notification bar. API 21 new @ Override public void onListenerConnected () {super. OnListenerConnected (); }}Copy the code

NotificationListenerService also provides cancelNotification and cancelAllNotification method, is used to remove the notification bar, can easily achieve in custom remove notice the lock screen interface.

When the author implemented this class, he found a trap. All the codes were OK, and the right to use the notification bar was authorized, but there was no onNotificationPosted callback. After a long time of checking the problem, he found that someone on the Internet had the same problem, and created a new class to copy the code, and it was OK. This seems to be a compiler problem.

The Service that acquired the right to use the notification bar is naturally preserved, and if killed, the Android system can restart it. So when you see applications that require access to the notification bar, be aware that these applications are permanently resident in the background. Of course, if a Service crashes a certain number of times, the Android system will become frustrated and will not restart the Service until the next shutdown and restart, so it is best to keep the Service in a lightweight independent process.

2. Obtain the HotSeat shortcut

Desktop shortcuts are divided into two types: Desktop area, which refers to the part that scrolls with the screen, and HotSeat area, which refers to the part placed at the bottom of the Desktop that does not scroll with the screen. User-defined HotSeat shortcuts are common applications. It would be a friendly feature to add this quick startup to the lock screen. The main problem with this is how to get the HotSeat shortcut.

The system shortcuts are stored in the Favorites table in the database file launcher.db, as shown:



You can see the shortcut ID, title, and intent. The container property is used to indicate the id of the folder, but you can see that some containers are negative. This is why I looked at the source code related to Android Launcher and found two sentences:

/**
* The icon is a resource identified by a package name and an integer id.
*/
public static final int CONTAINER_DESKTOP = -100;
public static final int CONTAINER_HOTSEAT = -101;Copy the code

That is, a shortcut with a container of -100 is in the Desktop area, and a shortcut with a container of -101 is in the HotSeat area. Now that you know how to store the shortcut, the next problem is to find the path to the launcher.db file. In different versions of Android native apis, the package name of the launcher used by default is different, and the path to the launcher.

Android API 7 and below: / data/data/com. Android. The launcher/databases/launcher. 8 to 18 db Android API: / data/data/com. Android. Launcher2 / databases/launcher db android API and above: /data/data/com.android.launcher3/databases/laucher.dbCopy the code

The path gets even messier for various third-party ROMs with weird Laucher package names:

HTC: / data/data/com. HTC. The launcher/databases/launcher. 360: db/data/data/net. Qihoo360. The launcher/databases/launcher db huawei: / data/data/com. Huawei. Launcher3 / databases/launcher db millet: / data/data/com. Beautiful miui. Mihome2 / databases/launcher. The db...Copy the code

Of course, we don’t get shortcut information by reading the database directly. Laucher provides a ContentProvider for external reading. Avoid the database path to do compatibility pit, suddenly fell into another pit, through the Provider to read shortcuts, the required permissions and URIs also need to do compatibility.

From the shortcut storage can be seen, the Android fragmentation is how severe, so finally I decided to no longer compatible to realize deeply, it is not worth the cost behavior, are interested can look at this article, judge whether a shortcut is how hard it is: www.jianshu.com/p/dc3d04337…

3. Get wallpaper

The background of the lock screen should be consistent with the mobile desktop wallpaper so that users will not feel awkward. There are two ways to obtain the wallpaper.

The Activity Style pattern

If you are implementing a lock screen for your Activity, you can directly set the Activity theme to use the wallpaper as the background.

android:theme="@android:style/Theme.Wallpaper.NoTitleBar"Copy the code

WallPaperManager mode

The lock screen in hover mode does not use theme, so you can get the wallpaper via WallPaperManager.

WallpaperManager WallpaperManager = wallPaperManager.getInstance (this); / / get the current wallpaper Drawable wallpaperDrawable = wallpaperManager. GetDrawable (); Bitmap bm = ((BitmapDrawable) wallpaperDrawable).getBitmap(); mRootView.setBackgroundDrawable(new BitmapDrawable(bm));Copy the code

This is OK on xiaomi’s one-screen imitation iOS desktop, but on native Android’s two-screen desktop (shortcuts and all apps are on different screens), the shortcut screen gets a whole large wallpaper, while Laucher actually shows the cut wallpaper. So above means can set the wallpaper that dimension does not match for setting. The total number of screens of Laucher can be found in the workspaceScreens table in the above launcher. Db, but the specific screen is stored in the memory instance of launcher App, which cannot be obtained. If you really want to cut, it is recommended to directly cut the left side of the entire wallpaper according to the width and height of the screen.


This work is licensed by Creative Commons Attribution 3.0 Mainland China license, welcome to reprint, but please indicate that it is from Li Zhengxian, attach the original link, and keep the content of the article complete after reprint. I reserve all rights related to copyright. Link to this article: www.lizhengxian.top/all-lock/