Android Question How to program the Always On Display?

HappyDad

Member
Licensed User
Longtime User
Is there a way to program the Always On Display, with B4A?
For example, drawing on the screen, while the screen is off.
It's like displaying the time and notifications when the screen is off.
I am using Samsung Note10+.
The thing I want to do is to draw a breathing light notification when a message is received but not checked.
 

KZero

Active Member
Licensed User
Longtime User
it's possible but there is no official API from Samsung to do it.

check this may be useful
 
Upvote 0

wes58

Active Member
Licensed User
Longtime User
Is there a way to program the Always On Display, with B4A?
For example, drawing on the screen, while the screen is off.
It's like displaying the time and notifications when the screen is off.
I am using Samsung Note10+.
The thing I want to do is to draw a breathing light notification when a message is received but not checked.
If you know a little bit of java you could do something.
Read this: https://forum.xda-developers.com/galaxy-s10/themes/app-holey-light-t3917675
And here is the link to the code on GitHub https://github.com/Chainfire/HoleyLight. It creates a circle around the camera cutout (for Samsung Galaxy s10) with colour changing depending on the type of notification.
It is possible to create a library using part of this code. I did it a while back for my Galaxy S10, but you would have to spend some time trying to understand the code.
In the end, I just decided to do it simpler way (not worrying about displaying additional coloured circle) but controlling the AOD such that is is only on (displaying time and notification icon) when there is a new notification, and when the notification is cleared the AOD is off.
 
Upvote 0

HappyDad

Member
Licensed User
Longtime User
If you know a little bit of java you could do something.
Read this: https://forum.xda-developers.com/galaxy-s10/themes/app-holey-light-t3917675
And here is the link to the code on GitHub https://github.com/Chainfire/HoleyLight. It creates a circle around the camera cutout (for Samsung Galaxy s10) with colour changing depending on the type of notification.
It is possible to create a library using part of this code. I did it a while back for my Galaxy S10, but you would have to spend some time trying to understand the code.
In the end, I just decided to do it simpler way (not worrying about displaying additional coloured circle) but controlling the AOD such that is is only on (displaying time and notification icon) when there is a new notification, and when the notification is cleared the AOD is off.
This is a good workaround. Could you please point me the way of how to control the AOD to be on and off?
 
Upvote 0

wes58

Active Member
Licensed User
Longtime User
This is a good workaround. Could you please point me the way of how to control the AOD to be on and off?
This is not really workaround. I had both options working and saw that adding an overlay window with a flashing circle increases a bit current consumption, so I decided to just control AOD.

As I said, to get it working you need to know a bit of java.
1. You have to build an AODHelper.app - an Android Studio project is on the GitHub link I in the previous post. This app is in the folder "Helper". You need this, because this app has to be compiled with compile SDK version 22. Any higher version won't allow you to write to system settings, which is needed for changing AOD.
You can use the pre-built application but, then you won't have all the functions I use.
I have changed the code (and use different package name) in this app adding more functions:
B4X:
package eu.chainfire.holeylight.aodhelper;

import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import android.content.ContentResolver;
import android.content.Context;

public class AodHelper{
    public int aodModeGet(Context context){
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return 0;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            int mode = Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
            Log.d("B4A", "AOD tapToShow mode = " + mode);
            mode = Settings.System.getInt(resolver, "aod_mode", -1);
            return mode;
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 5;
        }
    }

    public int aodTapToShow(Context context, boolean enable) {
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return 0;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            int mode = Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
            Log.d("B4A", "AOD tapToShow mode = " + mode);
            mode = Settings.System.getInt(resolver, "aod_mode", -1);
            android.provider.Settings.System.putInt(resolver, "aod_mode", enable ? 1 : 0);
            android.provider.Settings.System.putInt(resolver, "aod_tap_to_show_mode", 0);
            return 1;
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 2;
        }
    }

    public int aodShow(Context context, boolean enable) {
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return 0;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            int mode = Settings.System.getInt(resolver, "aod_mode", -1);
            Log.d("B4A", "AOD enabled = " + mode);
            android.provider.Settings.System.putInt(resolver, "aod_mode", enable ? 1 : 0);
            return 1;
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 2;
        }
    }
}
This application doesn't show anything when you click on it.

2. Add this java code in your B4A application. I added it to code module named "codejava"
B4X:
#if JAVA
import android.os.Build;
import android.provider.Settings;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import java.util.List;
import java.lang.Boolean;
import android.util.Log;

    private static Boolean helperPackageFound = null;
    private static int result = -1;
    
    private static Intent getIntent(int enabled, String mode) {
        Intent intent = new Intent("PACKAGE_NAME_FOR_AODHELPERAPP.SET_AOD");
        intent.setClassName("PACKAGE_NAME_FOR_AODHELPERAPP", "PACKAGE_NAME_FOR_AODHELPERAPP.AODReceiver");
        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
        intent.putExtra(mode + ".enable", enabled);
//        Log.d("B4A", intent.toString());
        return intent;
    }
    
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public static boolean haveHelperPackage(Context context, boolean refresh) {
        if ((helperPackageFound != null) && !refresh) return helperPackageFound.booleanValue();
        
        List<ResolveInfo> resolves = context.getPackageManager().queryBroadcastReceivers(getIntent(1, "SET_TAP_TO_SHOW"), 0);
        for (ResolveInfo info : resolves) {
            if (info.activityInfo != null) {
                if (info.activityInfo.packageName.equals("com.NotifyMe.aodHelper")) {
                    helperPackageFound = true;
//                    Log.d("B4A", "helperPackageFound = " + helperPackageFound);
                    return true;
                }
            }
        }
//        Log.d("B4A", "helperPackageFound = " + helperPackageFound);
        helperPackageFound = false;
        return false;
    }
    
    @SuppressWarnings("deprecation")
    public static int setAOD(Context context, int enabled, String mode) {
        context.sendOrderedBroadcast(getIntent(enabled, mode), null, new BroadcastReceiver() {
            @SuppressWarnings("all")
            @Override
            public void onReceive(Context context, Intent intent) {
//                Log.d("B4A", "getResultCode = " + getResultCode());
                result = getResultCode();
//                if (getResultCode() != 1) {

                    //TODO raise the alarms, something went awry
//                }
            }
        }, null, 0, null, null);
        return result;
    }

/*    public static String getAODThemePackage(Context context) {
        ContentResolver resolver = context.getContentResolver();
        return android.provider.Settings.System.getString(resolver, "current_sec_aod_theme_package");
    }
*/
    public static int[] getAODSchedule(Context context) {
        ContentResolver resolver = context.getContentResolver();
        int start = android.provider.Settings.System.getInt(resolver, "aod_mode_start_time", 0);
        int end = android.provider.Settings.System.getInt(resolver, "aod_mode_end_time", 0);
        return new int[] { start, end };
    }
    
    public static int aodIsTapToShow(Context context){
        ContentResolver resolver = context.getContentResolver();
        return Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
    }
    
    public static int aodIsEnabled(Context context){
        ContentResolver resolver = context.getContentResolver();
        return Settings.System.getInt(resolver, "aod_mode", -1);
    }
    
    public static int aodTapToShow(Context context, int state) {
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return -1;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            if(Settings.System.getInt(resolver, "aod_mode", -1) == 0){        //disabled so turn off
                android.provider.Settings.System.putInt(resolver, "aod_mode", 1);
            }
            int mode = Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
            if(mode != state){
                android.provider.Settings.System.putInt(resolver, "aod_tap_to_show_mode", state);
            }
            return 1; // Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 2;
        }
    }   
    
    public static int aodEnable(Context context, int state) {
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return 0;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            int mode = Settings.System.getInt(resolver, "aod_mode", -1);
            if(mode != state){
                android.provider.Settings.System.putInt(resolver, "aod_mode", state);
            }
            return 1; //Settings.System.getInt(resolver, "aod_mode", -1);
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 2;
        }
    }   
#End If
In function "getIntent" replace PACKAGE_NAME_FOR_AODHELPERAPP with the AOD helper app package name.

3. And here is some code to control AOD
First you have to check that the AODHelper app is installed.
B4X:
    Dim jo, context As JavaObject
    Dim Refresh As Boolean = False
    Dim res, res1 As Int
    
    context = context.InitializeStatic("anywheresoftware.b4a.BA").GetField("applicationContext")
    jo.InitializeStatic(Application.PackageName & ".codejava")       
    Refresh = jo.RunMethod("haveHelperPackage", Array(context, Refresh))
If Refresh is TRUE then you can continue.
Some functions:
B4X:
    res = jo.RunMethod("aodIsEnabled", Array(context))  'res=0 aod not enabled, 1= enabled

    res = jo.RunMethod("setAOD", Array(context, 1, "SET_AOD"))    'enable AOD
    res = jo.RunMethod("setAOD", Array(context, 0, "SET_AOD"))    'disable AOD

    res = jo.RunMethod("aodIsTapToShow", Array(context))        'check current mode

    If res = 0 Then  'not in tap to show mode
    'you can get the aod schedule if it is set
        Dim i() As Int =  jo.RunMethod("getAODSchedule", Array(context))
        Dim schedule As String
        If i.Length = 2 Then
            Log("AOD start time " & i(0) & ", end time " & i(1))
            If i(0) = i(1) Then
                schedule = schedule & CRLF & "Display Mode - DISPLAY ALWAYS"
            Else
                schedule = schedule & CRLF & "Display Mode - DISPLAY AS SCHEDULED" & CRLF & "Schedule: "
                Dim h, m As Int
                Dim s As String
                h = i(0) / 60
                m = i(0) - (h * 60)
                s = m
                If m = 0 Then
                    s = s & "0"
                End If
                schedule = schedule & h & ":" & s & " - "
                h = i(1) / 60
                m = i(1) - (h * 60)
                s = m
                If m = 0 Then
                    s = s & "0"
                End If
                schedule = schedule & h & ":" & s
                
            End If
        End If
    End If
    
    
    'enable AOD to tap to show mode   
    res = jo.RunMethod("setAOD", Array(context, 1, "SET_TAP_TO_SHOW"))

    'disable AOD tap to show mode   
    res = jo.RunMethod("setAOD", Array(context, 0, "SET_TAP_TO_SHOW"))
Hope that this will help. Good Luck.
 
Upvote 0

HappyDad

Member
Licensed User
Longtime User
Thanks!
I will take some time to study the code.

This is not really workaround. I had both options working and saw that adding an overlay window with a flashing circle increases a bit current consumption, so I decided to just control AOD.

As I said, to get it working you need to know a bit of java.
1. You have to build an AODHelper.app - an Android Studio project is on the GitHub link I in the previous post. This app is in the folder "Helper". You need this, because this app has to be compiled with compile SDK version 22. Any higher version won't allow you to write to system settings, which is needed for changing AOD.
You can use the pre-built application but, then you won't have all the functions I use.
I have changed the code (and use different package name) in this app adding more functions:
B4X:
package eu.chainfire.holeylight.aodhelper;

import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import android.content.ContentResolver;
import android.content.Context;

public class AodHelper{
    public int aodModeGet(Context context){
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return 0;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            int mode = Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
            Log.d("B4A", "AOD tapToShow mode = " + mode);
            mode = Settings.System.getInt(resolver, "aod_mode", -1);
            return mode;
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 5;
        }
    }

    public int aodTapToShow(Context context, boolean enable) {
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return 0;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            int mode = Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
            Log.d("B4A", "AOD tapToShow mode = " + mode);
            mode = Settings.System.getInt(resolver, "aod_mode", -1);
            android.provider.Settings.System.putInt(resolver, "aod_mode", enable ? 1 : 0);
            android.provider.Settings.System.putInt(resolver, "aod_tap_to_show_mode", 0);
            return 1;
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 2;
        }
    }

    public int aodShow(Context context, boolean enable) {
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return 0;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            int mode = Settings.System.getInt(resolver, "aod_mode", -1);
            Log.d("B4A", "AOD enabled = " + mode);
            android.provider.Settings.System.putInt(resolver, "aod_mode", enable ? 1 : 0);
            return 1;
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 2;
        }
    }
}
This application doesn't show anything when you click on it.

2. Add this java code in your B4A application. I added it to code module named "codejava"
B4X:
#if JAVA
import android.os.Build;
import android.provider.Settings;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import java.util.List;
import java.lang.Boolean;
import android.util.Log;

    private static Boolean helperPackageFound = null;
    private static int result = -1;
   
    private static Intent getIntent(int enabled, String mode) {
        Intent intent = new Intent("PACKAGE_NAME_FOR_AODHELPERAPP.SET_AOD");
        intent.setClassName("PACKAGE_NAME_FOR_AODHELPERAPP", "PACKAGE_NAME_FOR_AODHELPERAPP.AODReceiver");
        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
        intent.putExtra(mode + ".enable", enabled);
//        Log.d("B4A", intent.toString());
        return intent;
    }
   
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public static boolean haveHelperPackage(Context context, boolean refresh) {
        if ((helperPackageFound != null) && !refresh) return helperPackageFound.booleanValue();
       
        List<ResolveInfo> resolves = context.getPackageManager().queryBroadcastReceivers(getIntent(1, "SET_TAP_TO_SHOW"), 0);
        for (ResolveInfo info : resolves) {
            if (info.activityInfo != null) {
                if (info.activityInfo.packageName.equals("com.NotifyMe.aodHelper")) {
                    helperPackageFound = true;
//                    Log.d("B4A", "helperPackageFound = " + helperPackageFound);
                    return true;
                }
            }
        }
//        Log.d("B4A", "helperPackageFound = " + helperPackageFound);
        helperPackageFound = false;
        return false;
    }
   
    @SuppressWarnings("deprecation")
    public static int setAOD(Context context, int enabled, String mode) {
        context.sendOrderedBroadcast(getIntent(enabled, mode), null, new BroadcastReceiver() {
            @SuppressWarnings("all")
            @Override
            public void onReceive(Context context, Intent intent) {
//                Log.d("B4A", "getResultCode = " + getResultCode());
                result = getResultCode();
//                if (getResultCode() != 1) {

                    //TODO raise the alarms, something went awry
//                }
            }
        }, null, 0, null, null);
        return result;
    }

/*    public static String getAODThemePackage(Context context) {
        ContentResolver resolver = context.getContentResolver();
        return android.provider.Settings.System.getString(resolver, "current_sec_aod_theme_package");
    }
*/
    public static int[] getAODSchedule(Context context) {
        ContentResolver resolver = context.getContentResolver();
        int start = android.provider.Settings.System.getInt(resolver, "aod_mode_start_time", 0);
        int end = android.provider.Settings.System.getInt(resolver, "aod_mode_end_time", 0);
        return new int[] { start, end };
    }
   
    public static int aodIsTapToShow(Context context){
        ContentResolver resolver = context.getContentResolver();
        return Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
    }
   
    public static int aodIsEnabled(Context context){
        ContentResolver resolver = context.getContentResolver();
        return Settings.System.getInt(resolver, "aod_mode", -1);
    }
   
    public static int aodTapToShow(Context context, int state) {
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return -1;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            if(Settings.System.getInt(resolver, "aod_mode", -1) == 0){        //disabled so turn off
                android.provider.Settings.System.putInt(resolver, "aod_mode", 1);
            }
            int mode = Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
            if(mode != state){
                android.provider.Settings.System.putInt(resolver, "aod_tap_to_show_mode", state);
            }
            return 1; // Settings.System.getInt(resolver, "aod_tap_to_show_mode", -1);
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 2;
        }
    }  
   
    public static int aodEnable(Context context, int state) {
        if(Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
            return 0;
        }
        try {
            ContentResolver resolver = context.getContentResolver();
            int mode = Settings.System.getInt(resolver, "aod_mode", -1);
            if(mode != state){
                android.provider.Settings.System.putInt(resolver, "aod_mode", state);
            }
            return 1; //Settings.System.getInt(resolver, "aod_mode", -1);
        }
        catch (Exception e) {
            // no permissions
            e.printStackTrace();
            return 2;
        }
    }  
#End If
In function "getIntent" replace PACKAGE_NAME_FOR_AODHELPERAPP with the AOD helper app package name.

3. And here is some code to control AOD
First you have to check that the AODHelper app is installed.
B4X:
    Dim jo, context As JavaObject
    Dim Refresh As Boolean = False
    Dim res, res1 As Int
   
    context = context.InitializeStatic("anywheresoftware.b4a.BA").GetField("applicationContext")
    jo.InitializeStatic(Application.PackageName & ".codejava")      
    Refresh = jo.RunMethod("haveHelperPackage", Array(context, Refresh))
If Refresh is TRUE then you can continue.
Some functions:
B4X:
    res = jo.RunMethod("aodIsEnabled", Array(context))  'res=0 aod not enabled, 1= enabled

    res = jo.RunMethod("setAOD", Array(context, 1, "SET_AOD"))    'enable AOD
    res = jo.RunMethod("setAOD", Array(context, 0, "SET_AOD"))    'disable AOD

    res = jo.RunMethod("aodIsTapToShow", Array(context))        'check current mode

    If res = 0 Then  'not in tap to show mode
    'you can get the aod schedule if it is set
        Dim i() As Int =  jo.RunMethod("getAODSchedule", Array(context))
        Dim schedule As String
        If i.Length = 2 Then
            Log("AOD start time " & i(0) & ", end time " & i(1))
            If i(0) = i(1) Then
                schedule = schedule & CRLF & "Display Mode - DISPLAY ALWAYS"
            Else
                schedule = schedule & CRLF & "Display Mode - DISPLAY AS SCHEDULED" & CRLF & "Schedule: "
                Dim h, m As Int
                Dim s As String
                h = i(0) / 60
                m = i(0) - (h * 60)
                s = m
                If m = 0 Then
                    s = s & "0"
                End If
                schedule = schedule & h & ":" & s & " - "
                h = i(1) / 60
                m = i(1) - (h * 60)
                s = m
                If m = 0 Then
                    s = s & "0"
                End If
                schedule = schedule & h & ":" & s
               
            End If
        End If
    End If
   
   
    'enable AOD to tap to show mode  
    res = jo.RunMethod("setAOD", Array(context, 1, "SET_TAP_TO_SHOW"))

    'disable AOD tap to show mode  
    res = jo.RunMethod("setAOD", Array(context, 0, "SET_TAP_TO_SHOW"))
Hope that this will help. Good Luck.
 
Upvote 0
Top