Migrate to Android 11, API 30.

This commit is contained in:
Onuray Sahin 2021-01-04 17:12:49 +03:00
parent f1f1613f00
commit 23623b8895
21 changed files with 424 additions and 322 deletions

View file

@ -15,10 +15,10 @@ Translations 🗣:
-
SDK API changes ⚠️:
-
- Increase targetSdkVersion to 30 (#2600)
Build 🧱:
-
- Compile with Android SDK 30 (Android 11)
Test:
-

View file

@ -32,11 +32,11 @@ buildscript {
}
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 30
versionCode 1
versionName "1.0"
}

View file

@ -24,9 +24,12 @@ import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.WindowManager
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
@ -89,14 +92,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This is important for the dispatchTouchEvent, if not we must correct
// the touch coordinates
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_IMMERSIVE)
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
setDecorViewFullScreen()
views = ActivityAttachmentViewerBinding.inflate(layoutInflater)
setContentView(views.root)
@ -132,6 +128,25 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
}
}
@Suppress("DEPRECATION")
private fun setDecorViewFullScreen() {
// This is important for the dispatchTouchEvent, if not we must correct
// the touch coordinates
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE // New API instead of SYSTEM_UI_FLAG_IMMERSIVE
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // New API instead of FLAG_TRANSLUCENT_STATUS
window.navigationBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // new API instead of FLAG_TRANSLUCENT_NAVIGATION
} else {
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_IMMERSIVE)
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
}
}
fun onSelectedPositionChanged(position: Int) {
attachmentsAdapter.recyclerView?.findViewHolderForAdapterPosition(currentPosition)?.let {
(it as? BaseViewHolder)?.onSelected(false)
@ -311,28 +326,42 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
?.handleCommand(commands)
}
@Suppress("DEPRECATION")
private fun hideSystemUI() {
systemUiVisibility = false
// Enables regular immersive mode.
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) // new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE // New API instead of SYSTEM_UI_FLAG_IMMERSIVE
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // New API instead of FLAG_TRANSLUCENT_STATUS
window.navigationBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // new API instead of FLAG_TRANSLUCENT_NAVIGATION
} else {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
}
}
// Shows the system bars by removing all the flags
// except for the ones that make the content appear under the system bars.
@Suppress("DEPRECATION")
private fun showSystemUI() {
systemUiVisibility = true
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
} else {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<color name="half_transparent_status_bar">#80000000</color>
</resources>

View file

@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 30
versionCode 1
versionName "1.0"

View file

@ -14,12 +14,12 @@ buildscript {
}
android {
compileSdkVersion 29
compileSdkVersion 30
testOptions.unitTests.includeAndroidResources = true
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 30
versionCode 1
versionName "0.0.1"
// Multidex is useful for tests

View file

@ -47,22 +47,24 @@ internal object ThumbnailExtractor {
val mediaMetadataRetriever = MediaMetadataRetriever()
try {
mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
val thumbnail = mediaMetadataRetriever.frameAtTime
val outputStream = ByteArrayOutputStream()
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
val thumbnailWidth = thumbnail.width
val thumbnailHeight = thumbnail.height
val thumbnailSize = outputStream.size()
thumbnailData = ThumbnailData(
width = thumbnailWidth,
height = thumbnailHeight,
size = thumbnailSize.toLong(),
bytes = outputStream.toByteArray(),
mimeType = MimeTypes.Jpeg
)
thumbnail.recycle()
outputStream.reset()
mediaMetadataRetriever.frameAtTime?.let { thumbnail ->
val outputStream = ByteArrayOutputStream()
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
val thumbnailWidth = thumbnail.width
val thumbnailHeight = thumbnail.height
val thumbnailSize = outputStream.size()
thumbnailData = ThumbnailData(
width = thumbnailWidth,
height = thumbnailHeight,
size = thumbnailSize.toLong(),
bytes = outputStream.toByteArray(),
mimeType = MimeTypes.Jpeg
)
thumbnail.recycle()
outputStream.reset()
} ?: run {
Timber.e("Cannot extract video thumbnail at %s", attachment.queryUri.toString())
}
} catch (e: Exception) {
Timber.e(e, "Cannot extract video thumbnail")
} finally {

View file

@ -19,11 +19,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
targetSdkVersion 30
versionCode 1
versionName "1.0"

View file

@ -58,7 +58,7 @@ class AudioPicker : Picker<MultiPickerAudioType>() {
context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
val mediaMetadataRetriever = MediaMetadataRetriever()
mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
}
audioList.add(

View file

@ -61,10 +61,10 @@ class VideoPicker : Picker<MultiPickerVideoType>() {
context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
val mediaMetadataRetriever = MediaMetadataRetriever()
mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt()
height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt()
orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION).toInt()
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt() ?: 0
height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt() ?: 0
orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)?.toInt() ?: 0
}
videoList.add(

View file

@ -101,7 +101,7 @@ ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].
def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
android {
compileSdkVersion 29
compileSdkVersion 30
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
// Ref: https://issuetracker.google.com/issues/144111441
@ -111,7 +111,7 @@ android {
applicationId "im.vector.app"
// Set to API 21: see #405
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 30
multiDexEnabled true
// `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode.

View file

@ -23,6 +23,7 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.WindowInsetsController
import android.view.WindowManager
import android.widget.TextView
import androidx.annotation.AttrRes
@ -33,6 +34,7 @@ import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
@ -410,13 +412,21 @@ abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScr
/**
* Force to render the activity in fullscreen
*/
@Suppress("DEPRECATION")
private fun setFullScreen() {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE // New API instead of SYSTEM_UI_FLAG_IMMERSIVE
window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.attachmentviewer.R.color.half_transparent_status_bar) // New API instead of FLAG_TRANSLUCENT_STATUS
window.navigationBarColor = ContextCompat.getColor(this, im.vector.lib.attachmentviewer.R.color.half_transparent_status_bar) // new API instead of FLAG_TRANSLUCENT_NAVIGATION
} else {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
}
}
/* ==========================================================================================

View file

@ -153,8 +153,13 @@ class AttachmentsPreviewFragment @Inject constructor(
)
}
@Suppress("DEPRECATION")
private fun applyInsets() {
view?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
activity?.window?.setDecorFitsSystemWindows(false)
} else {
view?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
}
ViewCompat.setOnApplyWindowInsetsListener(views.attachmentPreviewerBottomContainer) { v, insets ->
v.updatePadding(bottom = insets.systemWindowInsetBottom)
insets

View file

@ -25,8 +25,11 @@ import android.os.Bundle
import android.os.Parcelable
import android.view.View
import android.view.Window
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.view.ViewCompat
import androidx.core.view.isInvisible
@ -102,29 +105,43 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
setContentView(R.layout.activity_call)
}
@Suppress("DEPRECATION")
private fun hideSystemUI() {
systemUiVisibility = false
// Enables regular immersive mode.
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) // new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE // New API instead of SYSTEM_UI_FLAG_IMMERSIVE
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // New API instead of FLAG_TRANSLUCENT_STATUS
window.navigationBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // new API instead of FLAG_TRANSLUCENT_NAVIGATION
} else {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
}
}
// Shows the system bars by removing all the flags
// except for the ones that make the content appear under the system bars.
@Suppress("DEPRECATION")
private fun showSystemUI() {
systemUiVisibility = true
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
} else {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
}
}
private fun toggleUiSystemVisibility() {

View file

@ -17,6 +17,7 @@
package im.vector.app.features.crypto.recover
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.view.KeyEvent
@ -102,7 +103,12 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetBoot
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = super.onCreateView(inflater, container, savedInstanceState)
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
dialog?.window?.setDecorFitsSystemWindows(false)
} else {
@Suppress("DEPRECATION")
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
}
return rootView
}

View file

@ -155,18 +155,15 @@ class LoginWebFragment @Inject constructor(
// avoid infinite onPageFinished call
if (url.startsWith("http")) {
// Generic method to make a bridge between JS and the UIWebView
val mxcJavascriptSendObjectMessage = assetReader.readAssetFile("sendObject.js")
view.loadUrl(mxcJavascriptSendObjectMessage)
assetReader.readAssetFile("sendObject.js")?.let { view.loadUrl(it) }
if (state.signMode == SignMode.SignIn) {
// The function the fallback page calls when the login is complete
val mxcJavascriptOnLogin = assetReader.readAssetFile("onLogin.js")
view.loadUrl(mxcJavascriptOnLogin)
assetReader.readAssetFile("onLogin.js")?.let { view.loadUrl(it) }
} else {
// MODE_REGISTER
// The function the fallback page calls when the registration is complete
val mxcJavascriptOnRegistered = assetReader.readAssetFile("onRegistered.js")
view.loadUrl(mxcJavascriptOnRegistered)
assetReader.readAssetFile("onRegistered.js")?.let { view.loadUrl(it) }
}
}
}

View file

@ -21,6 +21,7 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.widget.ImageView
import com.tapadoo.alerter.Alerter
import com.tapadoo.alerter.OnHideAlertListener
@ -165,9 +166,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
?.takeIf { ThemeUtils.isLightTheme(it) }
?.let { it.window?.decorView }
?.let { view ->
var flags = view.systemUiVisibility
flags = flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
view.systemUiVisibility = flags
view.windowInsetsController?.setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS)
}
}
@ -179,9 +178,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
?.takeIf { ThemeUtils.isLightTheme(it) }
?.let { it.window?.decorView }
?.let { view ->
var flags = view.systemUiVisibility
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
view.systemUiVisibility = flags
view.windowInsetsController?.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS)
}
}

View file

@ -21,7 +21,6 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.os.AsyncTask
import android.os.Build
import android.view.View
import androidx.fragment.app.DialogFragment
@ -37,6 +36,11 @@ import im.vector.app.features.settings.devtools.GossipingEventsSerializer
import im.vector.app.features.settings.locale.SystemLocaleProvider
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.version.VersionProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Call
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
@ -98,6 +102,8 @@ class BugReporter @Inject constructor(
var screenshot: Bitmap? = null
private set
private val coroutineScope = CoroutineScope(SupervisorJob())
private val LOGCAT_CMD_ERROR = arrayOf("logcat", // /< Run 'logcat' command
"-d", // /< Dump the log rather than continue outputting it
"-v", // formatting
@ -160,286 +166,287 @@ class BugReporter @Inject constructor(
withScreenshot: Boolean,
theBugDescription: String,
listener: IMXBugReportListener?) {
object : AsyncTask<Void, Int, String>() {
// enumerate files to delete
val mBugReportFiles: MutableList<File> = ArrayList()
// enumerate files to delete
val mBugReportFiles: MutableList<File> = ArrayList()
override fun doInBackground(vararg voids: Void?): String? {
var bugDescription = theBugDescription
var serverError: String? = null
val crashCallStack = getCrashDescription(context)
coroutineScope.executeAsyncTask(
onPreExecute = {
// NOOP
},
doInBackground = { publishProgress: suspend (progress: Int) -> Unit ->
var bugDescription = theBugDescription
var serverError: String? = null
val crashCallStack = getCrashDescription(context)
if (null != crashCallStack) {
bugDescription += "\n\n\n\n--------------------------------- crash call stack ---------------------------------\n"
bugDescription += crashCallStack
}
val gzippedFiles = ArrayList<File>()
if (withDevicesLogs) {
val files = vectorFileLogger.getLogFiles()
files.mapNotNullTo(gzippedFiles) { f ->
if (!mIsCancelled) {
compressFile(f)
} else {
null
}
if (null != crashCallStack) {
bugDescription += "\n\n\n\n--------------------------------- crash call stack ---------------------------------\n"
bugDescription += crashCallStack
}
}
if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) {
val gzippedLogcat = saveLogCat(context, false)
val gzippedFiles = ArrayList<File>()
if (null != gzippedLogcat) {
if (gzippedFiles.size == 0) {
gzippedFiles.add(gzippedLogcat)
} else {
gzippedFiles.add(0, gzippedLogcat)
if (withDevicesLogs) {
val files = vectorFileLogger.getLogFiles()
files.mapNotNullTo(gzippedFiles) { f ->
if (!mIsCancelled) {
compressFile(f)
} else {
null
}
}
}
val crashDescription = getCrashFile(context)
if (crashDescription.exists()) {
val compressedCrashDescription = compressFile(crashDescription)
if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) {
val gzippedLogcat = saveLogCat(context, false)
if (null != compressedCrashDescription) {
if (null != gzippedLogcat) {
if (gzippedFiles.size == 0) {
gzippedFiles.add(compressedCrashDescription)
gzippedFiles.add(gzippedLogcat)
} else {
gzippedFiles.add(0, compressedCrashDescription)
gzippedFiles.add(0, gzippedLogcat)
}
}
val crashDescription = getCrashFile(context)
if (crashDescription.exists()) {
val compressedCrashDescription = compressFile(crashDescription)
if (null != compressedCrashDescription) {
if (gzippedFiles.size == 0) {
gzippedFiles.add(compressedCrashDescription)
} else {
gzippedFiles.add(0, compressedCrashDescription)
}
}
}
}
}
activeSessionHolder.getSafeActiveSession()
?.takeIf { !mIsCancelled && withKeyRequestHistory }
?.cryptoService()
?.getGossipingEvents()
?.let { GossipingEventsSerializer().serialize(it) }
?.toByteArray()
?.let { rawByteArray ->
File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME)
.also {
it.outputStream()
.use { os -> os.write(rawByteArray) }
}
}
?.let { compressFile(it) }
?.let { gzippedFiles.add(it) }
var deviceId = "undefined"
var userId = "undefined"
var olmVersion = "undefined"
activeSessionHolder.getSafeActiveSession()?.let { session ->
userId = session.myUserId
deviceId = session.sessionParams.deviceId ?: "undefined"
olmVersion = session.cryptoService().getCryptoVersion(context, true)
}
if (!mIsCancelled) {
val text = "[Element] " +
if (forSuggestion) {
"[Suggestion] "
} else {
""
} +
bugDescription
// build the multi part request
val builder = BugReporterMultipartBody.Builder()
.addFormDataPart("text", text)
.addFormDataPart("app", "riot-android")
.addFormDataPart("user_agent", Matrix.getInstance(context).getUserAgent())
.addFormDataPart("user_id", userId)
.addFormDataPart("device_id", deviceId)
.addFormDataPart("version", versionProvider.getVersion(longFormat = true, useBuildNumber = false))
.addFormDataPart("branch_name", context.getString(R.string.git_branch_name))
.addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
.addFormDataPart("olm_version", olmVersion)
.addFormDataPart("device", Build.MODEL.trim())
.addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff())
.addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
.addFormDataPart("os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") "
+ Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME)
.addFormDataPart("locale", Locale.getDefault().toString())
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
val buildNumber = context.getString(R.string.build_number)
if (buildNumber.isNotEmpty() && buildNumber != "0") {
builder.addFormDataPart("build_number", buildNumber)
}
// add the gzipped files
for (file in gzippedFiles) {
builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
}
mBugReportFiles.addAll(gzippedFiles)
if (withScreenshot) {
val bitmap = screenshot
if (null != bitmap) {
val logCatScreenshotFile = File(context.cacheDir.absolutePath, LOG_CAT_SCREENSHOT_FILENAME)
if (logCatScreenshotFile.exists()) {
logCatScreenshotFile.delete()
activeSessionHolder.getSafeActiveSession()
?.takeIf { !mIsCancelled && withKeyRequestHistory }
?.cryptoService()
?.getGossipingEvents()
?.let { GossipingEventsSerializer().serialize(it) }
?.toByteArray()
?.let { rawByteArray ->
File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME)
.also {
it.outputStream()
.use { os -> os.write(rawByteArray) }
}
}
?.let { compressFile(it) }
?.let { gzippedFiles.add(it) }
try {
logCatScreenshotFile.outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
var deviceId = "undefined"
var userId = "undefined"
var olmVersion = "undefined"
activeSessionHolder.getSafeActiveSession()?.let { session ->
userId = session.myUserId
deviceId = session.sessionParams.deviceId ?: "undefined"
olmVersion = session.cryptoService().getCryptoVersion(context, true)
}
if (!mIsCancelled) {
val text = "[Element] " +
if (forSuggestion) {
"[Suggestion] "
} else {
""
} +
bugDescription
// build the multi part request
val builder = BugReporterMultipartBody.Builder()
.addFormDataPart("text", text)
.addFormDataPart("app", "riot-android")
.addFormDataPart("user_agent", Matrix.getInstance(context).getUserAgent())
.addFormDataPart("user_id", userId)
.addFormDataPart("device_id", deviceId)
.addFormDataPart("version", versionProvider.getVersion(longFormat = true, useBuildNumber = false))
.addFormDataPart("branch_name", context.getString(R.string.git_branch_name))
.addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
.addFormDataPart("olm_version", olmVersion)
.addFormDataPart("device", Build.MODEL.trim())
.addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff())
.addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
.addFormDataPart("os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") "
+ Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME)
.addFormDataPart("locale", Locale.getDefault().toString())
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
val buildNumber = context.getString(R.string.build_number)
if (buildNumber.isNotEmpty() && buildNumber != "0") {
builder.addFormDataPart("build_number", buildNumber)
}
// add the gzipped files
for (file in gzippedFiles) {
builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
}
mBugReportFiles.addAll(gzippedFiles)
if (withScreenshot) {
val bitmap = screenshot
if (null != bitmap) {
val logCatScreenshotFile = File(context.cacheDir.absolutePath, LOG_CAT_SCREENSHOT_FILENAME)
if (logCatScreenshotFile.exists()) {
logCatScreenshotFile.delete()
}
builder.addFormDataPart("file",
logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
} catch (e: Exception) {
Timber.e(e, "## sendBugReport() : fail to write screenshot$e")
try {
logCatScreenshotFile.outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
builder.addFormDataPart("file",
logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
} catch (e: Exception) {
Timber.e(e, "## sendBugReport() : fail to write screenshot$e")
}
}
}
}
screenshot = null
screenshot = null
// add some github labels
builder.addFormDataPart("label", BuildConfig.VERSION_NAME)
builder.addFormDataPart("label", BuildConfig.FLAVOR_DESCRIPTION)
builder.addFormDataPart("label", context.getString(R.string.git_branch_name))
// add some github labels
builder.addFormDataPart("label", BuildConfig.VERSION_NAME)
builder.addFormDataPart("label", BuildConfig.FLAVOR_DESCRIPTION)
builder.addFormDataPart("label", context.getString(R.string.git_branch_name))
// Special for RiotX
builder.addFormDataPart("label", "[Element]")
// Special for RiotX
builder.addFormDataPart("label", "[Element]")
// Suggestion
if (forSuggestion) {
builder.addFormDataPart("label", "[Suggestion]")
}
// Suggestion
if (forSuggestion) {
builder.addFormDataPart("label", "[Suggestion]")
}
if (getCrashFile(context).exists()) {
builder.addFormDataPart("label", "crash")
deleteCrashFile(context)
}
if (getCrashFile(context).exists()) {
builder.addFormDataPart("label", "crash")
deleteCrashFile(context)
}
val requestBody = builder.build()
val requestBody = builder.build()
// add a progress listener
requestBody.setWriteListener { totalWritten, contentLength ->
val percentage = if (-1L != contentLength) {
if (totalWritten > contentLength) {
100
// add a progress listener
requestBody.setWriteListener { totalWritten, contentLength ->
val percentage = if (-1L != contentLength) {
if (totalWritten > contentLength) {
100
} else {
(totalWritten * 100 / contentLength).toInt()
}
} else {
(totalWritten * 100 / contentLength).toInt()
0
}
} else {
0
if (mIsCancelled && null != mBugReportCall) {
mBugReportCall!!.cancel()
}
Timber.v("## onWrite() : $percentage%")
suspend { publishProgress(percentage) }
}
if (mIsCancelled && null != mBugReportCall) {
mBugReportCall!!.cancel()
// build the request
val request = Request.Builder()
.url(context.getString(R.string.bug_report_url))
.post(requestBody)
.build()
var responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR
var response: Response? = null
var errorMessage: String? = null
// trigger the request
try {
mBugReportCall = mOkHttpClient.newCall(request)
response = mBugReportCall!!.execute()
responseCode = response.code
} catch (e: Exception) {
Timber.e(e, "response")
errorMessage = e.localizedMessage
}
Timber.v("## onWrite() : $percentage%")
publishProgress(percentage)
}
// if the upload failed, try to retrieve the reason
if (responseCode != HttpURLConnection.HTTP_OK) {
if (null != errorMessage) {
serverError = "Failed with error $errorMessage"
} else if (null == response || null == response.body) {
serverError = "Failed with error $responseCode"
} else {
try {
val inputStream = response.body!!.byteStream()
// build the request
val request = Request.Builder()
.url(context.getString(R.string.bug_report_url))
.post(requestBody)
.build()
var responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR
var response: Response? = null
var errorMessage: String? = null
// trigger the request
try {
mBugReportCall = mOkHttpClient.newCall(request)
response = mBugReportCall!!.execute()
responseCode = response.code
} catch (e: Exception) {
Timber.e(e, "response")
errorMessage = e.localizedMessage
}
// if the upload failed, try to retrieve the reason
if (responseCode != HttpURLConnection.HTTP_OK) {
if (null != errorMessage) {
serverError = "Failed with error $errorMessage"
} else if (null == response || null == response.body) {
serverError = "Failed with error $responseCode"
} else {
try {
val inputStream = response.body!!.byteStream()
serverError = inputStream.use {
buildString {
var ch = it.read()
while (ch != -1) {
append(ch.toChar())
ch = it.read()
serverError = inputStream.use {
buildString {
var ch = it.read()
while (ch != -1) {
append(ch.toChar())
ch = it.read()
}
}
}
}
// check if the error message
try {
val responseJSON = JSONObject(serverError)
serverError = responseJSON.getString("error")
} catch (e: JSONException) {
Timber.e(e, "doInBackground ; Json conversion failed")
}
// check if the error message
try {
val responseJSON = JSONObject(serverError)
serverError = responseJSON.getString("error")
} catch (e: JSONException) {
Timber.e(e, "doInBackground ; Json conversion failed")
}
// should never happen
if (null == serverError) {
serverError = "Failed with error $responseCode"
// should never happen
if (null == serverError) {
serverError = "Failed with error $responseCode"
}
} catch (e: Exception) {
Timber.e(e, "## sendBugReport() : failed to parse error")
}
} catch (e: Exception) {
Timber.e(e, "## sendBugReport() : failed to parse error")
}
}
}
}
return serverError
}
override fun onProgressUpdate(vararg progress: Int?) {
if (null != listener) {
try {
listener.onProgress(progress[0] ?: 0)
} catch (e: Exception) {
Timber.e(e, "## onProgress() : failed")
}
}
}
override fun onPostExecute(reason: String?) {
mBugReportCall = null
// delete when the bug report has been successfully sent
for (file in mBugReportFiles) {
file.delete()
}
if (null != listener) {
try {
if (mIsCancelled) {
listener.onUploadCancelled()
} else if (null == reason) {
listener.onUploadSucceed()
} else {
listener.onUploadFailed(reason)
serverError
},
onProgressUpdate = { progress ->
if (null != listener) {
try {
listener.onProgress(progress)
} catch (e: Exception) {
Timber.e(e, "## onProgress() : failed")
}
}
},
onPostExecute = { reason: String? ->
mBugReportCall = null
// delete when the bug report has been successfully sent
for (file in mBugReportFiles) {
file.delete()
}
if (null != listener) {
try {
if (mIsCancelled) {
listener.onUploadCancelled()
} else if (null == reason) {
listener.onUploadSucceed()
} else {
listener.onUploadFailed(reason)
}
} catch (e: Exception) {
Timber.e(e, "## onPostExecute() : failed")
}
} catch (e: Exception) {
Timber.e(e, "## onPostExecute() : failed")
}
}
}
}.execute()
)
}
/**
@ -696,4 +703,21 @@ class BugReporter @Inject constructor(
return null
}
fun <P, R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit,
doInBackground: suspend (suspend (P) -> Unit) -> R,
onPostExecute: (R) -> Unit,
onProgressUpdate: (P) -> Unit
) = launch {
onPreExecute()
val result = withContext(Dispatchers.IO) {
doInBackground {
withContext(Dispatchers.Main) { onProgressUpdate(it) }
}
}
onPostExecute(result)
}
}

View file

@ -16,6 +16,7 @@
package im.vector.app.features.roomprofile.uploads.media
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
@ -78,9 +79,14 @@ class RoomUploadsMediaFragment @Inject constructor(
controller.listener = this
}
@Suppress("DEPRECATION")
private fun getNumberOfColumns(): Int {
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
requireContext().display?.getMetrics(displayMetrics)
} else {
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
}
return dimensionConverter.pxToDp(displayMetrics.widthPixels) / IMAGE_SIZE_DP
}

View file

@ -64,7 +64,9 @@ class VectorWebViewActivity : VectorBaseActivity<ActivityVectorWebViewBinding>()
// Allow use of Local Storage
domStorageEnabled = true
@Suppress("DEPRECATION")
allowFileAccessFromFileURLs = true
@Suppress("DEPRECATION")
allowUniversalAccessFromFileURLs = true
displayZoomControls = false
@ -73,7 +75,7 @@ class VectorWebViewActivity : VectorBaseActivity<ActivityVectorWebViewBinding>()
val cookieManager = android.webkit.CookieManager.getInstance()
cookieManager.setAcceptThirdPartyCookies(views.simpleWebview, true)
val url = intent.extras?.getString(EXTRA_URL)
val url = intent.extras?.getString(EXTRA_URL) ?: return
val title = intent.extras?.getString(EXTRA_TITLE, USE_TITLE_FROM_WEB_PAGE)
if (title != USE_TITLE_FROM_WEB_PAGE) {
setTitle(title)

View file

@ -54,7 +54,9 @@ fun WebView.setupForWidget(webViewEventListener: WebViewEventListener) {
// Allow use of Local Storage
settings.domStorageEnabled = true
@Suppress("DEPRECATION")
settings.allowFileAccessFromFileURLs = true
@Suppress("DEPRECATION")
settings.allowUniversalAccessFromFileURLs = true
settings.displayZoomControls = false
@ -75,7 +77,6 @@ fun WebView.clearAfterWidget() {
// Make sure you remove the WebView from its parent view before doing anything.
(parent as? ViewGroup)?.removeAllViews()
webChromeClient = null
webViewClient = null
clearHistory()
// NOTE: clears RAM cache, if you pass true, it will also clear the disk cache.
clearCache(true)