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;
}
}
}