[Wish] system window (kiosk variant mode)

peacemaker

Expert
Licensed User
Longtime User
Any interest from Guru to make this code (system full-screen window) on B4A to have a possibility of the true kiosk mode ?


MainActivity.java
B4X:
package rubberbigpepper.fullscreenunclosable;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.view.View.onClickListener;

public class MainActivity extends Activity implements onClickListener
{
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);       
        setContentView(R.layout.activity_main);
        findViewById(R.id.btnStart).setonClickListener(this);
    }
    
    public void onClick(View v) 
    {
        switch(v.getId())
        {
        case R.id.btnStart:
            StartService();
            break;
        }
    }
    
    private void StartService()
    {
        try
        {
            Intent cIntent=new Intent(this,FrontWindowService.class);
            cIntent.setAction(FrontWindowService.ACTION_START);
            startService(cIntent);
        }
        catch(Exception ex){}
    }
}

Service:
B4X:
package rubberbigpepper.fullscreenunclosable;

import java.lang.reflect.Method;

import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.View.onClickListener;

public class FrontWindowService extends Service implements onClickListener
{
    public static String ACTION_START="rubberbigpepper.StartFrontWindowService";
    public static String ACTION_STOP="rubberbigpepper.StopFrontWindowService";
    
    private View m_cLayout=null;
    //private FrameLayout m_cBrLayout=null;
    private Method mSetForeground;
    private Method mStartForeground;
    private Method mStopForeground;
    private static final Class<?>[] mSetForegroundSignature = new Class[] {boolean.class};
    private static final Class<?>[] mStartForegroundSignature = new Class[] {int.class, Notification.class};
    private static final Class<?>[] mStopForegroundSignature = new Class[] { boolean.class};
    private Object[] mSetForegroundArgs = new Object[1];
    private Object[] mStartForegroundArgs = new Object[2];
    private Object[] mStopForegroundArgs = new Object[1];
    
    
    void invokeMethod(Method method, Object[] args) 
    {
        try 
        {
            mStartForeground.invoke(this, mStartForegroundArgs);
        } 
        catch (Exception e) 
        {
        } 
    }
    
    void startForegroundCompat(int id, Notification notification)
    {
        // If we have the new startForeground API, then use it.
        if (mStartForeground != null) 
        {
            mStartForegroundArgs[0] = Integer.valueOf(id);
            mStartForegroundArgs[1] = notification;
            invokeMethod(mStartForeground, mStartForegroundArgs);
            return;
        }

        // Fall back on the old API.
        mSetForegroundArgs[0] = Boolean.TRUE;
        invokeMethod(mSetForeground, mSetForegroundArgs);
    }
    
    void stopForegroundCompat(int id) 
    {
        // If we have the new stopForeground API, then use it.
        if (mStopForeground != null) 
        {
            mStopForegroundArgs[0] = Boolean.TRUE;
            try 
            {
                mStopForeground.invoke(this, mStopForegroundArgs);
            } 
            catch (Exception e) 
            {
                // Should not happen.
                Log.w("ApiDemos", "Unable to invoke stopForeground", e);
            } 
            return;
        }

        // Fall back on the old API.  Note to cancel BEFORE changing the
        // foreground state, since we could be killed at that point.
        mSetForegroundArgs[0] = Boolean.FALSE;
        invokeMethod(mSetForeground, mSetForegroundArgs);
    }
    
    @Override
    public void onCreate() 
    {
        try 
        {
            mStartForeground = getClass().getMethod("startForeground",mStartForegroundSignature);
            mStopForeground = getClass().getMethod("stopForeground",mStopForegroundSignature);
        } 
        catch (Exception e) 
        {
            // Running on an older platform.
            mStartForeground = mStopForeground = null;
            //return;
        }
        try 
        {
            mSetForeground = getClass().getMethod("setForeground",mSetForegroundSignature);
        } 
        catch (Exception e) 
        {
            throw new IllegalStateException(
                    "OS doesn't have Service.startForeground OR Service.setForeground!");
        }
    }

    @Override
    public IBinder onBind(Intent arg0) 
    {
        // TODO Auto-generated method stub
        return null;
    }
    
    @Override
    public void onStart(Intent intent, int startId)
    {
        handleCommand(intent);
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
    {
        handleCommand(intent);
        return Service.START_REDELIVER_INTENT;
    }
    public void handleCommand(Intent intent) 
    {        
        if(intent==null)
            return;
        String strAction=intent.getAction();
        if(strAction.equalsIgnoreCase(ACTION_START))
        {//пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ
            HideFullScreenWindow();
            ShowFullScreenWindow();
            startForegroundCompat(R.string.app_name, new Notification());
        }
        else
        {
            HideFullScreenWindow();
            stopSelf();
        }
    }
    
    private void HideFullScreenWindow()
    {
        try
        {
            if(m_cLayout!=null)
            {
                WindowManager cWM=(WindowManager)getSystemService(Context.WINDOW_SERVICE);
                cWM.removeView(m_cLayout);
                m_cLayout=null;
            }
        }
        catch(Exception ex){}
    }
    
    private void ShowFullScreenWindow()
    {
        WindowManager cWM=(WindowManager)getSystemService(Context.WINDOW_SERVICE);
        LayoutInflater cInflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        m_cLayout=cInflater.inflate(R.layout.front_main, null);
        m_cLayout.findViewById(R.id.btnClose).setonClickListener(this);
        int nFlags=WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParam
s.FLAG_NOT_FOCUSABLE;
        nFlags|=WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN|WindowManager.Layo
utParams.FLAG_LAYOUT_INSET_DECOR;
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.FILL_PARENT,WindowManager.LayoutParams.FILL_PARE
NT,
                    WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,nFlags,PixelFormat.OPAQUE);
         cWM.addView(m_cLayout, lp);
    }
    

    public void onClick(View v) 
    {
        switch(v.getId())
        {
        case R.id.btnClose://удаляем окно
            HideFullScreenWindow();
            stopSelf();
            break;
        }
    }

}

Manifest:
B4X:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="rubberbigpepper.fullscreenunclosable"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />
    <uses-permission android:name="android.permission.SYSTEM_alert_WINDOW"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" 
            android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:exported="true" android:name="FrontWindowService" android:enabled="true"></service>
    </application>

</manifest>
 
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
This is an interesting solution.

I've played with it a bit. You can test this "kiosk" activity:
B4X:
Sub Activity_Create(FirstTime As Boolean)
   Dim btnStart As Button
   btnStart.Initialize("btnStart")
   btnStart.Text = "Start Kiosk"
   Activity.AddView(btnStart, 10dip, 10dip, 100dip, 100dip)
End Sub
Private Sub ShowFullScreenWindow
   Dim r As Reflector
   r.Target = r.GetContext
   Dim cwm As Object = r.RunMethod2("getSystemService", "window", "java.lang.String")
   Dim p As Panel
   p.Initialize("p")
   Dim lp As Object
   Dim i As String = "java.lang.int"
   lp = r.CreateObject2("android.view.WindowManager$LayoutParams", _
      Array As Object(-1, -1, 2010, 65832, -1), Array As String(i, i, i, i, i))
   r.Target = cwm
   r.RunMethod4("addView", Array As Object(p, lp), _
      Array As String("android.view.View", "android.view.ViewGroup$LayoutParams"))
   Dim btnEnd As Button
   btnEnd.Initialize("btnEnd")
   btnEnd.Text = "Exit Kiosk"
   p.AddView(btnEnd, 10dip, 10dip, 100dip, 100dip)
End Sub
Sub Activity_Resume

End Sub
Sub btnEnd_Click
   ExitApplication
End Sub
Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub btnStart_Click
   ShowFullScreenWindow
End Sub

Manifest editor:
B4X:
AddPermission(android.permission.SYSTEM_ALERT_WINDOW)

The problem is that the activity doesn't behave 100% properly as it is supposed to be a system alert activity.

In this case once the user presses on the home screen button the activity will stop responding.

It may be possible to use this method together with the kiosk implementation based on a service that restarts the activity.
 

sanjibnanda

Active Member
Licensed User
Longtime User

peacemaker

Expert
Licensed User
Longtime User
Erel, but how to remove\kill such system window from a service that showed it?
 

peacemaker

Expert
Licensed User
Longtime User
No. I tried this code to show firstly a small system window (not covering whole the screen) by a service. As if to close the activity - it's correct - window is not controllable.

B4X:
Sub GetScreenDim
Dim r As Reflector
r.Target = r.GetContext
r.Target = r.RunMethod("getResources")
r.Target = r.RunMethod("getDisplayMetrics")
'Log(r.GetField("xdpi"))
'Log(r.GetField("ydpi"))
heightPixels = r.GetField("heightPixels")
widthPixels = r.GetField("widthPixels")
End Sub

Sub ShowSystemWindow (myText As String, w As Int, h As Int, x As Int, y As Int)
x = x - widthPixels/2
y = y - heightPixels/2

Dim r As Reflector
r.Target = r.GetContext
Dim cwm As Object = r.RunMethod2("getSystemService", "window", "java.lang.String")
If SysWindowShown Then   'remove
   Dim t As View
   t = Main.SysLabel
   t.RemoveView
End If

Dim p As Label
p.Initialize("p")
p.Text=myText
Dim lp As Object
Dim i As String = "java.lang.int"
'full screen: lp = r.CreateObject2("android.view.WindowManager$LayoutParams", Array As Object(-1, -1, 2010, 65832, -1), Array As String(i, i, i, i, i))
'small window test lp = r.CreateObject2("android.view.WindowManager$LayoutParams", Array As Object(50, 50, 2006, 65832, -1), Array As String(i, i, i, i, i))
'p.Text = "99%"
lp = r.CreateObject2("android.view.WindowManager$LayoutParams", Array As Object(w, h, x, y, 2010, 65832, -1), Array As String(i, i, i, i, i, i, i))
r.Target = cwm
r.RunMethod4("addView", Array As Object(p, lp), _
    Array As String("android.view.View", "android.view.ViewGroup$LayoutParams"))
Main.SysLabel = p
SysWindowShown = True
End Sub

Task is to update the text on such system window any time.
System windows can be added, but previous one must be closed before the next.
 

peacemaker

Expert
Licensed User
Longtime User
Eril, how to remove this created system window without exiting the app ?
Activity.Finish does not help :)

ExitApplication helps, but ... how to enter the password ? Make window smaller ?
 
Last edited:
Top