0

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

DSlomer64
  • 4,234
  • 4
  • 53
  • 88
  • 1
    https://stackoverflow.com/questions/3599563/alert-dialog-from-android-service you can try this – nishant Jun 16 '18 at 19:29
  • 1
    Your IntentService may be working when there're no Activities around. – Anatolii Jun 16 '18 at 19:54
  • @nishant--Thanks. The link refers us to `android-smspopup`, which supposely does exactly that. It looks like a lot more effort than I have energy/patience for right now, but I'll keep it in mind. – DSlomer64 Jun 19 '18 at 14:21
  • @user8035311--good point. I'm self-taught at Java and Android and that pretty much means I assumed my prior programming expertise entitled me to just jump in, apply what I know, and ask about what I don't know, the result being gaping holes in my knowledge base and understanding. But your comment did indeed fill one of those holes. Thanks. The more I think about it, the clearer `Context` becomes. *Clearer*, still not totally clear. – DSlomer64 Jun 19 '18 at 14:24

3 Answers3

1

You should use the activity context for this error and i hope resolved this error.

private Context context

initialise this context in onCreate method like this

context = this; // and use this context

showMessage(context, "title", "note", mDatabase);

And also create a context type variable in singleton class use as a global verialbe

Vijendra patidar
  • 1,444
  • 1
  • 14
  • 24
  • @Vijendra--I tried using `public Context gContext` in `MainActivity`, which might be okay if I'd planned for the app to be a singleton. Anyway, I wound up with the same error message. I thought I got errors with `showMessage(context...` with `context = this`, but it's been a few days and now I can't replicate it. I'll try to do so and get back to you. However, where I want to show the message is in an `IntentService`, not an `Activity`, so there's no `onCreate` to initialize in. – DSlomer64 Jun 19 '18 at 14:30
1

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);
}
Anatolii
  • 14,139
  • 4
  • 35
  • 65
  • @user805311--The fact is that I'd already tried doing more or less what you've suggested before I read your reply. However my approach definitely isn't as sound as yours appears to be. I couldn't apply it because `BROADCAST_FILTER` isn't to be found in the `Intent` world. I'm going to provide my own "Answer", explaining how I (had) applied your suggestion but give you credit for the Answer, as I'm about to do. Thank you. – DSlomer64 Jun 19 '18 at 14:35
  • @DSlomer64 Thanks for your comment and sorry for omitting the definition of BROADCAST_FILTER. I edited my answer - hopefully it is a bit clearer now. – Anatolii Jun 19 '18 at 16:50
0

Because of the nature of my app, I already had a BroadcastManager and a BroadcastReceiver in place before asking my question, so I had already tried a weak attempt of my own, similar to what @user805311 suggested.

And I was essentially right in saying I just shouldn't try to showMessage in an IntentService, though @nishant's link shows how it can be done.

The changes I made to existing code were as follows:

In publishProgress in the IntentService for QueryDb, I added code to the if at the top to deliver the message (stored in progress) via another Intent instead of showMessage, which caused exceptions, and immediately began shutting down the process via onPostExecute:

public class QueryDb extends IntentService
{
  ...
  private void publishProgress(String progress) 
  {
    Intent intent = new Intent(MatchesActivity.SOME_ACTION);

    if(progress.toLowerCase().startsWith("Revise"))
    {
      intent.putExtra(MatchesActivity.STRING_EXTRA_NAME, progress);
      onPostExecute();
    }
    else ...
    ...
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

It's called like so at the point during execution where going further would be fruitless:

  String messageText = "Revise code!";
  publishProgress(messageText);

The published message text is interpreted like so, inside MatchesActivity, which had initiated the IntentService and begun to listen for messages.

This is the first place showMessage finally worked.

public class MatchesActivity extends Activity implements QueryDb.DbProcListenerForQueryDb
{
  private BroadcastReceiver mMessageReceiver  = new BroadcastReceiver()
  {
    @Override public void onReceive(Context context, Intent intent)
    {
      String s = intent.getStringExtra(STRING_EXTRA_NAME) ;
      if(s.startsWith("Revise"))
      {
         showMessage(_context, "Oh, no...", s, null);
         MatchesActivity.this.setResult(RESULT_CANCELED);
      }
      else ...
      ...
    };  // BroadcastReceiver

But to continue with my quest to showMessage with acceptable Context, I removed the showMessage from the code just above and put one in onActivityResult in MainActivity, where it also worked in onActivityResult where the "bad news" about canceling was received and processed:

public class MainActivity extends Activity
{ 
  ...
  @Override protected void onActivityResult(int requestCode, int resultCode, Intent intentData)
  {
    if(resultCode != RESULT_OK)
    {
      showMessage(this, "Bad news", "Only Scrabble", mDatabase);
      return;
    }
    ...

Long story short, moving showMessage from QueryDb--which doesn't extend Activity--into MatchesActivity or MainActivity eliminated error about Context.

DSlomer64
  • 4,234
  • 4
  • 53
  • 88