2

I've got a bug from google play console with the following stack trace

android.view.WindowManager$BadTokenException: 
  at android.view.ViewRootImpl.setView (ViewRootImpl.java:1078)
  at android.view.WindowManagerGlobal.addView (WindowManagerGlobal.java:381)
  at android.view.WindowManagerImpl.addView (WindowManagerImpl.java:93)
  at android.app.Dialog.show (Dialog.java:470)
  at android.app.AlertDialog$Builder.show (AlertDialog.java:1151)
  at android.widget.VideoView$5.onError (VideoView.java:600)
  at android.media.MediaPlayer$EventHandler.handleMessage (MediaPlayer.java:4417)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loop (Looper.java:214)
  at android.app.ActivityThread.main (ActivityThread.java:7156)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:494)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:975)

I can't reproduce it locally and find what causes this problem. I found out that it could happen when application context is used in mediaController instead of activity, but I set it by getActivity(), so it seemed to be caused by something different. If there are any ideas of what could cause this crash and how to fix, I would be very grateful for your help!

    @EFragment(resName = "dvd_player")
public class DvdPlayerFragment extends BaseFragment implements OnPreparedListener, OnErrorListener, OnCompletionListener {

  private static final String TAG = "DvdPlayerFragment";
  @FragmentArg
  Dvd dvd;

  @ViewById(resName = "root")
  View rootView;

  @ViewById
  VideoView videoView;

  @ViewById
  ProgressBar progressIndicator;

  private MediaController mediaController;

  private Uri uri;

  private int savedPosition;

  private boolean paused;

  private MediaPlayer mediaPlayer;

  private boolean showedUserMessage = false;

  private Timer watchStatsTimer;
  private final String watchKey = UUID.randomUUID().toString();
  private long accumulatedPlayTime = 0;

  private long lastPushedAccumulatedPlayTime = 0;
  private Long lastServerUpdate = null;

  @Override
  public void onActivityCreated(Bundle bundle) {
    Log.d(TAG, "onActivityCreated: BEGIN");
    initView();
    Log.d(TAG, "onActivityCreated: END");
    super.onActivityCreated(bundle);
  }

  private void play() {
    Log.d(TAG, "play: ");
    if (uri != null) {
      int seekToPosition = videoView.getCurrentPosition() == 0 ? savedPosition : videoView.getCurrentPosition();
      playVideo(seekToPosition, !paused);
    } else {
      showProgressIndicator(true);
      getUrl();
    }

    showUserMessageIfNeeded();
  }

  @Background
  void getUrl() {
    final Url url = app.getDvdWatchUrl(dvd);
    if (url != null) {
      onVideoUrl(url.getLink());
    } else {
      showMessage("Could not load dvd.");
      showProgressIndicator(false);
    }
  }

  @UiThread
  void onVideoUrl(final String url) {
    uri = Uri.parse(url);
    playVideo(0, true);
  }

  @UiThread
  void playVideo(final int seekTo, final boolean start) {
    Log.d(TAG, "playVideo: ");
    initMediaController();
    videoView.seekTo(seekTo);

    if (start) {
      videoView.start();
    }
  }

  private void initMediaController() {
    Log.d(TAG, "initMediaController: BEGIN");
    if (isAdded()) {
      Log.d(TAG, "initMediaController: isAdded");

      if (mediaController == null || mediaController.getContext() != getActivity()) {

        mediaController = new MediaController(getActivity());
        mediaController.setAnchorView(videoView);
        videoView.setMediaController(mediaController);
        videoView.setOnPreparedListener(this);
        videoView.setOnCompletionListener(this);
        videoView.setOnErrorListener(this);

        setVideoUri(uri);
      }
    }
  }

  private void setVideoUri(final Uri uri) {
    try {
      Log.d(TAG, "setVideoUri: ");
      Method setVideoURIMethod = videoView.getClass().getMethod("setVideoURI", Uri.class, Map.class);
      Map<String, String> params = new HashMap<>(1);
      final String cred = app.getLoginUsername() + ":" + app.getLoginPassword();
      final String auth = "Basic " + Base64.encodeBytes(cred.getBytes(StandardCharsets.UTF_8));
      params.put("Authorization", auth);
      setVideoURIMethod.invoke(videoView, uri, params);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  void initView() {
    Log.d(TAG, "initView: BEGIN");
    rootView.setOnTouchListener(new OnTouchListener() {
      @Override
      public boolean onTouch(View view, MotionEvent event) {
        Log.d(TAG, "onTouch: ");
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
          if (mediaController != null && getActivity() != null &&
                  mediaController.getContext() == getActivity()) {
            if (!getActivity().isFinishing() && !mediaController.isAttachedToWindow()) {
              try {
                mediaController.show();
              } catch (IllegalStateException | NullPointerException e) {
                e.printStackTrace();
              }
            }
          } else {
            initMediaController();
          }
        }
        return true;
      }
    });
    setNavigationBarTitle(dvd.getLabel());
    alignTitleToRight(true);
    showBackButton(backTitle);
  }

  @Override
  public void onPrepared(MediaPlayer mp) {
    Log.d(TAG, "onPrepared: ");
    this.mediaPlayer = mp;
    showProgressIndicator(false);
    createWatchStatsTimer();
  }

  @Override
  public boolean onError(MediaPlayer mp, int i, int j) {
    Log.d(TAG, "onError: BEGIN");
    showProgressIndicator(false);
    destroyWatchStatsTimer();
    updateServer();
    Log.d(TAG, "onError: END");
    return false;
  }

  @Override
  public void onCompletion(MediaPlayer mp) {
  }

  private void createWatchStatsTimer() {
    Log.d(TAG, "createWatchStatsTimer: ");
    if (watchStatsTimer == null) {
      DvdWatchStatsTask task = new DvdWatchStatsTask();
      watchStatsTimer = new Timer();
      watchStatsTimer.schedule(task, 1000, 1000);
    }
  }

  private void destroyWatchStatsTimer() {
    Log.d(TAG, "destroyWatchStatsTimer: ");
    if (watchStatsTimer != null) {
      watchStatsTimer.cancel();
      watchStatsTimer.purge();
      watchStatsTimer = null;
    }
  }

  @Override
  public void onPause() {
    super.onPause();
    Log.d(TAG, "onPause: ");
    savedPosition = videoView.getCurrentPosition();
    paused = !videoView.isPlaying();
  }

  @Override
  public void onResume() {
    super.onResume();
    Log.d(TAG, "onResume: ");
    play();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: ");
    destroyWatchStatsTimer();
    if (accumulatedPlayTime > lastPushedAccumulatedPlayTime) {
      updateServer();
    }
  }

  @UiThread
  @IgnoreWhen(IgnoreWhen.State.VIEW_DESTROYED)
  void showProgressIndicator(final boolean show) {
    if (show) {
      progressIndicator.setVisibility(View.VISIBLE);
    } else {
      progressIndicator.setVisibility(View.GONE);
    }
  }

  @UiThread
  void showUserMessageIfNeeded() {
    Log.d(TAG, "showUserMessageIfNeeded: ");
    if (!showedUserMessage) {
      showedUserMessage = true;
      final String version = android.os.Build.VERSION.RELEASE;

      if (version.equals("4.4.3") || version.equals("4.4.4")) {
        final String message = "Video has known issues in Android " + version;
        showMessage(message);
      }
    }
  }

  @UiThread
  void showMessage(final String message) {
    Log.d(TAG, "showMessage: ");
    final Toast toast = Toast.makeText(getActivity(), message, Toast.LENGTH_LONG);
    toast.setGravity(Gravity.CENTER, 0, -150);
    toast.show();
    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.d(TAG, "run: toast show in UI thread");
        toast.show();
      }
    }, 2500);
  }

  private void updateServer() {
    Log.d(TAG, "updateServer: ");
    try {
      Long percentage = 10000 * accumulatedPlayTime / mediaPlayer.getDuration();
      app.updateDvdWatchStats(dvd, watchKey, accumulatedPlayTime / 1000, percentage);
      Log.d("", "accumulatedPlayTime: " + accumulatedPlayTime + ", percentage: " + (float) percentage / 100);
    } catch (Exception ex) {
      Log.e(TAG, "updateServer: ", ex);
      ex.printStackTrace();
    }
    lastPushedAccumulatedPlayTime = accumulatedPlayTime;
    lastServerUpdate = new Date().getTime();
  }

  class DvdWatchStatsTask extends TimerTask {

    @Override
    public void run() {
      if (mediaPlayer != null) {
        try {
          if (mediaPlayer.isPlaying()) {
            // add a second to counter
            accumulatedPlayTime += 1000;
            Log.d(TAG, "run: accumulatedPlayTime " + accumulatedPlayTime);
          }
        } catch (Exception e) {
          Log.e(TAG, "run: ", e);
          e.printStackTrace();
        }

        if (shouldUpdateServer()) {
          updateServer();
        }
      }
    }

    private boolean shouldUpdateServer() {
      final long now = new Date().getTime();
      Log.d(TAG, "shouldUpdateServer: " + now);
      boolean playingAndShouldUpdate = false;
      try {
        playingAndShouldUpdate = mediaPlayer.isPlaying() && (lastServerUpdate == null || (now - lastServerUpdate > 29300));
      } catch (Exception e) {
        Log.e(TAG, "shouldUpdateServer: ", e);
        e.printStackTrace();
      }

      // paused or completed
      boolean notPlayingAndShouldUpdate = lastServerUpdate != null && (now - lastServerUpdate) > 29300 && accumulatedPlayTime > lastPushedAccumulatedPlayTime;

      return playingAndShouldUpdate || notPlayingAndShouldUpdate;
    }
  }
}
  • Does this answer your question? ["android.view.WindowManager$BadTokenException: Unable to add window" on buider.show()](https://stackoverflow.com/questions/18662239/android-view-windowmanagerbadtokenexception-unable-to-add-window-on-buider-s) – Dennis Kozevnikoff Aug 12 '21 at 12:06
  • This is definitely helpful, thank you! But in this case the problem is that dialog with error is shown not directly from my DvdPlayerFragment, but from VideoView onError() method, to which I have no access, so how can I prohibit showing the dialog, if activity is finishing, there? – Юлия Белик Aug 12 '21 at 13:08
  • I tried to trigger an onError() method when the activity is onStop state and it didn't caused the crash, seems that problem is actually in something else – Юлия Белик Aug 12 '21 at 13:31

0 Answers0