Search code examples
androidandroid-contextandroid-intentservice

What context to pass from an IntentHandler to a method that needs one?


I just added a check for a new condition that can arise in a long-working app that uses "borrowed" code to display a message.

The statement is inside the IntentHandler for class DbQuery, which extends IntentService:

showMessage(getApplicationContext(), "title", "note", mDatabase); // LINE 576 ****

showMessage is defined in class Utilities and had always worked, until I decided to see that it works on my Android 4 tablet (19):

  public static void showMessage(Context mContext, String tit, String mm, SQLiteDatabase m)
  {
    TextView message = new TextView(mContext);
    TextView title   = new TextView(mContext);
    title.setText(tit);
    message.setText(mm);
    showCenteredInfoDialog(mContext, title, message, Gravity.CENTER, m); // **** LINE 505
  }

It calls the method where the exception occurs (marked with *****):

  public static void showCenteredInfoDialog
      (
              Context context, TextView message, TextView title, int 
              gravity, SQLiteDatabase m
      )
  {
    context.getSystemService(Context.LAYOUTINFLATERSERVICE);

    int YELLOWFOREGROUND ;
    int BRIGHTBLUEBACKGROUND ;

    {
      YELLOWFOREGROUND = context.getResources().getColor(R.color.yellowforeground, null);
      BRIGHTBLUEBACKGROUND =
          context.getResources().getColor(R.color.brightbluebackground, null);

      title.setTextColor(YELLOWFOREGROUND);
      title.setBackgroundColor(BRIGHTBLUEBACKGROUND);
      title.setGravity(Gravity.CENTER);

      AlertDialog.Builder
          builder = new AlertDialog.Builder(context);
          builder.setPositiveButton("OK", null);
          builder.setCustomTitle(title);
          builder.setMessage(message.getText());

      AlertDialog
          dialog;
          dialog = builder.show(); // *************************** LINE 482
          dialog.setCanceledOnTouchOutside(false);

      TextView
      messageView = /*(TextView)*/ dialog.findViewById(android.R.id.message);
      messageView.setTextColor(YELLOWFOREGROUND);
      messageView.setBackgroundColor(BRIGHTBLUEBACKGROUND);
      messageView.setTypeface(Typeface.MONOSPACE);
      messageView.setGravity(gravity);
    }
  }

Calls to showMessage work from MainActivity, where the Context passed is this.

I suppose I just shouldn't try to call showMessage from my IntentHandler, BUT I'd like to know how I SHOULD call it. I.e., what Context should I pass?

For the first argument to showMessage, I've tried this, getApplicationContext(), getApplication(), getApplication().getApplicationContext(), and getBaseContext(). All return the following error. I even supplied a listener instead of null as the 2nd argument to builder.setPositiveButton. Same error:

E: FATAL EXCEPTION: IntentService[QueryDb]
    Process: com.dslomer64.sqhell, PID: 24814
    android.view.WindowManager$BadTokenException: Unable to add window --
                                     token null is not for an application
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:571)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:310)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
        at android.app.Dialog.show(Dialog.java:319)
        at android.app.AlertDialog$Builder.show(AlertDialog.java:1112)
        at com.dslomer64.sqhell.Utilities.showCenteredInfoDialog(Utilities.java:482)
        at com.dslomer64.sqhell.Utilities.showMessage(Utilities.java:505)
        at com.dslomer64.sqhell.QueryDb.onHandleIntent(QueryDb.java:576)
        at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:66)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:148)
        at android.os.HandlerThread.run(HandlerThread.java:61)
E: getSlotFromBufferLocked: unknown buffer: 0xb40fe560

There follows a printscreen of part of a debug trace.

enter image description here


Solution

  • Generally speaking, Android Service may be running when there're no activities around. It's just another core entity in Android like Activity or BroadcastReceiver.

    It's cleaner, if your Android Service doesn't take the responsibility for updating the UI but rather sends enough data to your Activity that can use to update the UI. For instance, your Android Service could use Broadcast to send messages around and the Activity could listen to those messages via BroadcastReceiver.

    Define BROADCAST_FILTER in your Service so that BroadcastReceiver could identify that a message intent comes from the service:

    public static String BROADCAST_FILTER = <Your service class>.class.getName();
    

    Send an intent form your Service:

    //send a broadcast intent from your service
    Intent intent = new Intent();
    intent.setAction(BROADCAST_FILTER);
    intent.putExtra("message", "<your message here>")
    sendBroadcast(intent);
    

    Register BroadcastReceiver inside your Activity:

    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String message = intent.getStringExtra();
            Utils.showMessage(<your activity>.this, ...);
            //do your update here
        }
    };
    
    @Override
    protected void onResume() {
        super.onResume();
    
        IntentFilter filter = new IntentFilter();
        //make sure your onReceive receives only the messages with this action
        filter.addAction(<Your Service>.BROADCAST_FILTER);
        registerReceiver(receiver, filter);
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(receiver);
    }