diff --git a/app/build.gradle b/app/build.gradle index ee0b56df..83948077 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -178,8 +178,6 @@ dependencies { implementation 'com.google.android.exoplayer:exoplayer:2.10.4' - implementation 'me.drakeet.support:toastcompat:1.0.2' - implementation 'com.caverock:androidsvg-aar:1.4' } diff --git a/app/src/main/java/jp/juggler/util/ToastUtils.kt b/app/src/main/java/jp/juggler/util/ToastUtils.kt index cd183454..06c54aa5 100644 --- a/app/src/main/java/jp/juggler/util/ToastUtils.kt +++ b/app/src/main/java/jp/juggler/util/ToastUtils.kt @@ -2,6 +2,7 @@ package jp.juggler.util import android.content.Context import android.widget.Toast +import me.drakeet.support.toast.BadTokenListener import java.lang.ref.WeakReference import me.drakeet.support.toast.ToastCompat @@ -25,7 +26,7 @@ object ToastUtils { try { val duration = if(bLong) Toast.LENGTH_LONG else Toast.LENGTH_SHORT val t = ToastCompat.makeText(context, message, duration) - t.setBadTokenListener {} + t.setBadTokenListener{ } t.show() refToast = WeakReference(t) } catch(ex : Throwable) { diff --git a/app/src/main/java/me/drakeet/support/toast/BadTokenListener.kt b/app/src/main/java/me/drakeet/support/toast/BadTokenListener.kt new file mode 100644 index 00000000..78f5671a --- /dev/null +++ b/app/src/main/java/me/drakeet/support/toast/BadTokenListener.kt @@ -0,0 +1,10 @@ +package me.drakeet.support.toast + +import android.widget.Toast + +/** + * @author drakeet + */ +fun interface BadTokenListener { + fun onBadTokenCaught( toast : Toast) +} diff --git a/app/src/main/java/me/drakeet/support/toast/SafeToastContext.kt b/app/src/main/java/me/drakeet/support/toast/SafeToastContext.kt new file mode 100644 index 00000000..5c91689e --- /dev/null +++ b/app/src/main/java/me/drakeet/support/toast/SafeToastContext.kt @@ -0,0 +1,69 @@ +package me.drakeet.support.toast + +import android.content.Context +import android.content.ContextWrapper +import android.util.Log +import android.view.Display +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.view.WindowManager.BadTokenException +import android.widget.Toast + +/** + * @author drakeet + */ +internal class SafeToastContext(base : Context, private val toast : Toast) : ContextWrapper(base) { + + companion object { + private const val TAG = "WindowManagerWrapper" + } + + private var badTokenListener : BadTokenListener? = null + + fun setBadTokenListener(badTokenListener : BadTokenListener?) { + this.badTokenListener = badTokenListener + } + + override fun getApplicationContext() : Context = + ApplicationContextWrapper(baseContext.applicationContext) + + inner class ApplicationContextWrapper(base : Context) : ContextWrapper(base) { + + override fun getSystemService(name : String) : Any? = + if(WINDOW_SERVICE == name) { + // noinspection ConstantConditions + WindowManagerWrapper(baseContext.getSystemService(name) as WindowManager) + } else{ + super.getSystemService(name) + } + } + + inner class WindowManagerWrapper(private val base : WindowManager) : WindowManager { + + @Suppress("DEPRECATION") + override fun getDefaultDisplay() : Display? = + base.defaultDisplay + + override fun removeViewImmediate(view : View) = + base.removeViewImmediate(view) + + override fun updateViewLayout(view : View, params : ViewGroup.LayoutParams) = + base.updateViewLayout(view, params) + + override fun removeView(view : View) = + base.removeView(view) + + override fun addView(view : View, params : ViewGroup.LayoutParams) { + try { + Log.d(TAG, "WindowManager's addView(view, params) has been hooked.") + base.addView(view, params) + } catch(e : BadTokenException) { + e.message?.let { Log.i(TAG, it) } + badTokenListener?.onBadTokenCaught(toast) + } catch(throwable : Throwable) { + Log.e(TAG, "[addView]", throwable) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/drakeet/support/toast/ToastCompat.kt b/app/src/main/java/me/drakeet/support/toast/ToastCompat.kt new file mode 100644 index 00000000..9dbf01d5 --- /dev/null +++ b/app/src/main/java/me/drakeet/support/toast/ToastCompat.kt @@ -0,0 +1,126 @@ +package me.drakeet.support.toast + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.Resources +import android.os.Build +import android.view.View +import android.widget.Toast +import androidx.annotation.StringRes + +// original implementation is https://github.com/PureWriter/ToastCompat +// Android 11 でgetViewがnullを返すことが増えたので +/** + * @author drakeet + */ +/** + * Construct an empty Toast object. You must call [.setView] before you + * can call [.show]. + * + * @param context The context to use. Usually your [android.app.Application] + * or [android.app.Activity] object. + * @param baseToast The base toast + */ +@Suppress("DEPRECATION") +class ToastCompat( + context : Context, + private val baseToast : Toast +) : Toast(context) { + + companion object { + + /** + * Make a standard toast that just contains a text view. + * + * @param context The context to use. Usually your [android.app.Application] + * or [android.app.Activity] object. + * @param text The text to show. Can be formatted text. + * @param duration How long to display the message. Either [.LENGTH_SHORT] or + * [.LENGTH_LONG] + */ + @SuppressLint("ShowToast") + fun makeText(context : Context, text : CharSequence?, duration : Int) : ToastCompat { + // We cannot pass the SafeToastContext to Toast.makeText() because + // the View will unwrap the base context and we are in vain. + val toast = Toast.makeText(context, text, duration) + setContextCompat(toast.view) { SafeToastContext(context, toast) } + return ToastCompat(context, toast) + } + + /** + * Make a standard toast that just contains a text view with the text from a resource. + * + * @param context The context to use. Usually your [android.app.Application] + * or [android.app.Activity] object. + * @param resId The resource id of the string resource to use. Can be formatted text. + * @param duration How long to display the message. Either [.LENGTH_SHORT] or + * [.LENGTH_LONG] + * @throws Resources.NotFoundException if the resource can't be found. + */ + @Suppress("unused") + fun makeText(context : Context, @StringRes resId : Int, duration : Int) : Toast { + return makeText(context, context.resources.getText(resId), duration) + } + + private fun setContextCompat(view : View?, contextCreator : () -> Context) { + if(view != null && Build.VERSION.SDK_INT == 25) { + try { + val field = View::class.java.getDeclaredField("mContext") + field.isAccessible = true + field[view] = contextCreator() + } catch(throwable : Throwable) { + throwable.printStackTrace() + } + } + } + } + + fun setBadTokenListener(listener : BadTokenListener?) : ToastCompat { + (baseToast.view?.context as? SafeToastContext) + ?.setBadTokenListener(listener) + return this + } + + override fun setView(view : View) { + baseToast.view = view + setContextCompat(baseToast.view) { SafeToastContext(view.context, this) } + } + + override fun getView() : View? = baseToast.view + + override fun show() = baseToast.show() + + override fun setDuration(duration : Int) { + baseToast.duration = duration + } + + override fun setGravity(gravity : Int, xOffset : Int, yOffset : Int) = + baseToast.setGravity(gravity, xOffset, yOffset) + + override fun setMargin(horizontalMargin : Float, verticalMargin : Float) = + baseToast.setMargin(horizontalMargin, verticalMargin) + + override fun setText(resId : Int) = + baseToast.setText(resId) + + override fun setText(s : CharSequence) = + baseToast.setText(s) + + override fun getHorizontalMargin() : Float = + baseToast.horizontalMargin + + override fun getVerticalMargin() : Float = + baseToast.verticalMargin + + override fun getDuration() : Int = + baseToast.duration + + override fun getGravity() : Int = + baseToast.gravity + + override fun getXOffset() : Int = + baseToast.xOffset + + override fun getYOffset() : Int = + baseToast.yOffset +} \ No newline at end of file