MediaPlayer WindowManager$BadTokenException

Viewed 45

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;
    }
  }
}
0 Answers
Related