Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: Overlay on Window over Activities of a Task

I wanted to create an Overlay, like a HUD, that resides on the screen during my applications activity stack (the task of my app) changes.

I found a couple of examples using WindowManager but I couldn't figure out the parameterization for the correct z-index if you want. It was either to weak the next activity would go ontop of my overlay or to strong the overlay was a system wide overlay that was visible also when the app moved into the background.

I aim for displaying a view on top of all activites belonging to my app or task (app would be preferred). I am aware that apps and tasks are two different things on android...

What I explicitly don't want is to use system wide windows that require android.permission.SYSTEM_ALERT_WINDOW

--- my usecase --

I am implementing a flow that includes an activity providing a form for user input. that user input has to be processed in a rather complex manner, yielding a possible outcome of +/- 10 states. The processing can take up to 10 minutes and depending on the outcome of that process I want to display a corresponding view. While the process runs I intent to keep the user updated but can not allow him to navigate the app (except aborting the process). Every possible outcome of the long running operation will be presented in a different activity.

I am well aware, that there are several approaches possible (for example having one activity only). But that decision has already been made and is out of scope of that question. I have implemented a solution that uses System Windows to display that overlay in. For hiding the overlay I have to count onStart, onStop events and interpret "App did to move into background" and "App did move into foreground". This feels dirty and I am not satisfied with that solution. I'd rather take a step back and display my overlay on top of the calling activity and upon finishing the process hiding it and moving forward to the activity displaying the result.

One more thing I tried is to move a view from one activity to another. But this shows some flickering and interruption of my animation that I don't like.

I would appreciate it if we could focus on the question of whether its possible to display a view on top of the application/task window rather than inside a system or activity window ;)

like image 776
Jakob Avatar asked Sep 17 '13 07:09

Jakob


2 Answers

You really only have two options.

1) You can create an activity with the theme of Theme.Dialog. This will display a popup on top of your window. You can create the dialog to be modal-less (can click through). In my quick testing I wasn't able to get overlay to the edges of my screen although maybe modifying the theme would fix that.

MainActivity.java

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button test = (Button) this.findViewById(R.id.test);
        test.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ((Button) v).setBackgroundColor(Color.RED);
            }
        });

        Intent i = new Intent(this, SecondActivity.class);
        startActivity(i);
    }
}

SecondActivity.java

public class SecondActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_main);

        Window window = getWindow();
        window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
        window.setGravity(Gravity.BOTTOM);
    }

    @Override
    public void onBackPressed() {
            //Override to prevent back button from closing the second activity dialog
    }
}

Manifest

    ....
    <activity
        android:name="com.example.control.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name="com.example.control.SecondActivity"
        android:theme="@android:style/Theme.Dialog"
        android:label="@string/app_name" >
        ....
    </activity>
    ....

2) The second option is to use a SYSTEM_ALERT_WINDOW. I prefer this method significantly more. You are correct that it CAN be visible and on top of every other app, however, you can control when it is visible and when it isn't. I'm not going to post any source code but I will give you a general plan of attack.

When you create the service, bind to it using an AIDL. This way you'll be able to talk directly with the service to tell it when to 'hide' and 'show' the overlay. Speaking of hiding and showing, onPause and onResume can be used to tell the service to hide and show the overlay. Lastly, if you need to receive click events on your overlay, that will prove to be tricky as the touch events don't always act the way you expect them to.

Good Luck.

like image 90
Randy Avatar answered Oct 07 '22 13:10

Randy


I think the following will be of interest to you. You won't require android.permission.SYSTEM_ALERT_WINDOW. The code is for the Application class.

Comments should help you understand the logic. The code should work right out of the box.

public class YourApplication extends Application {

    // Popup to show persistent view
    PopupWindow pw;

    // View held by Popup
    LinearLayout ll;

    @Override
    public void onCreate() {
        super.onCreate();

        // Register for Activity Lifecyle Callbacks 
        registerActivityLifecycleCallbacks(new YourCallBack());

        // Initialize the view  
        ll = new LinearLayout(this);        
        ll.setLayoutParams(new LayoutParams(100, 100));     
        ll.setBackgroundColor(Color.BLUE);

        // Initialize popup 
        pw = new PopupWindow(ll, 100, 100);     

        // Set popup's window layout type to TYPE_TOAST     
        Method[] methods = PopupWindow.class.getMethods();
        for(Method m: methods){
            if(m.getName().equals("setWindowLayoutType")) {
                try{
                    m.invoke(pw, WindowManager.LayoutParams.TYPE_TOAST);
                }catch(Exception e){
                    e.printStackTrace();
                }
                break;
            }
        }       
    }

    @Override
    public void onTerminate() {
        super.onTerminate();

        if (pw != null && pw.isShowing()) {
            pw.dismiss();
        }
    };



    private final class YourCallBack implements ActivityLifecycleCallbacks {

        int numOfRunning = 0;

        @Override
        public void onActivityCreated(Activity arg0, Bundle arg1) { }

        @Override
        public void onActivityDestroyed(Activity arg0) { }

        @Override
        public void onActivityPaused(Activity arg0) {

            // An activity has been paused
            // Decrement count, but wait for a certain
            // period of time, in case another activity
            // from this application is being launched
            numOfRunning--;

            // Delay: 100 ms
            // If no activity's onResumed() was called,
            // its safe to assume that the application
            // has been paused, in which case, dismiss
            // the popup            
            new Handler().postDelayed(new Runnable() {

                @Override
                public void run() {
                    if (numOfRunning == 0) {
                        pw.dismiss();
                    }
                }
            }, 100L);
        }

        @Override
        public void onActivityResumed(Activity arg0) {

            // If no activities were running, show the popup
            if (numOfRunning == 0) {
                pw.showAtLocation(ll, Gravity.BOTTOM, 0, 0);
            }

            // Now, one activity is running         
            numOfRunning++;
        }

        @Override
        public void onActivitySaveInstanceState(Activity arg0, Bundle arg1) { }

        @Override
        public void onActivityStarted(Activity arg0) { }

        @Override
        public void onActivityStopped(Activity arg0) { }

    };
}
like image 8
Vikram Avatar answered Oct 07 '22 11:10

Vikram