/* * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:Suppress("DEPRECATION") package org.traccar.manager import android.Manifest import android.annotation.SuppressLint import android.app.Activity import android.content.ActivityNotFoundException import android.content.BroadcastReceiver import android.content.Context import android.content.DialogInterface import android.content.Intent import android.content.IntentFilter import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle import android.view.View import android.webkit.GeolocationPermissions import android.webkit.JavascriptInterface import android.webkit.ValueCallback import android.webkit.WebChromeClient import android.webkit.WebView import android.webkit.WebViewFragment import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.preference.PreferenceManager class MainFragment : WebViewFragment() { private lateinit var broadcastManager: LocalBroadcastManager inner class AppInterface { @JavascriptInterface fun postMessage(message: String) { if (message.contains("login")) { broadcastManager.sendBroadcast(Intent(EVENT_LOGIN)) } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) broadcastManager = LocalBroadcastManager.getInstance(activity) } @SuppressLint("SetJavaScriptEnabled") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) if ((activity.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0) { WebView.setWebContentsDebuggingEnabled(true) } webView.webChromeClient = webChromeClient webView.addJavascriptInterface(AppInterface(), "appInterface") val webSettings = webView.settings webSettings.javaScriptEnabled = true webSettings.domStorageEnabled = true webSettings.databaseEnabled = true webSettings.mediaPlaybackRequiresUserGesture = false val url = PreferenceManager.getDefaultSharedPreferences(activity) .getString(MainActivity.PREFERENCE_URL, null) url?.let { webView.loadUrl(it) } } private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val token = intent.getStringExtra(KEY_TOKEN) val code = "updateNotificationToken && updateNotificationToken('$token')" webView.evaluateJavascript(code, null) } } override fun onStart() { super.onStart() val intentFilter = IntentFilter(EVENT_TOKEN) broadcastManager.registerReceiver(broadcastReceiver, intentFilter) } override fun onStop() { super.onStop() broadcastManager.unregisterReceiver(broadcastReceiver) } private var openFileCallback: ValueCallback? = null private var openFileCallback2: ValueCallback>? = null override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { if (requestCode == REQUEST_FILE_CHOOSER) { val result = if (resultCode != Activity.RESULT_OK) null else data.data if (openFileCallback != null) { openFileCallback?.onReceiveValue(result) openFileCallback = null } if (openFileCallback2 != null) { openFileCallback2?.onReceiveValue(if (result != null) arrayOf(result) else arrayOf()) openFileCallback2 = null } } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { if (requestCode == REQUEST_PERMISSIONS_LOCATION) { val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED if (geolocationCallback != null) { geolocationCallback?.invoke(geolocationRequestOrigin, granted, false) geolocationRequestOrigin = null geolocationCallback = null } } } private var geolocationRequestOrigin: String? = null private var geolocationCallback: GeolocationPermissions.Callback? = null private val webChromeClient: WebChromeClient = object : WebChromeClient() { override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) { geolocationRequestOrigin = null geolocationCallback = null if (ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION)) { AlertDialog.Builder(activity) .setMessage(R.string.permission_location_rationale) .setNeutralButton(android.R.string.ok) { dialog: DialogInterface?, which: Int -> geolocationRequestOrigin = origin geolocationCallback = callback ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_PERMISSIONS_LOCATION ) } .show() } else { geolocationRequestOrigin = origin geolocationCallback = callback ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_PERMISSIONS_LOCATION ) } } else { callback.invoke(origin, true, false) } } // Android 4.1+ fun openFileChooser(uploadMessage: ValueCallback?, acceptType: String?, capture: String?) { openFileCallback = uploadMessage val intent = Intent(Intent.ACTION_GET_CONTENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "*/*" startActivityForResult( Intent.createChooser(intent, getString(R.string.file_browser)), REQUEST_FILE_CHOOSER ) } // Android 5.0+ override fun onShowFileChooser( mWebView: WebView, filePathCallback: ValueCallback>, fileChooserParams: FileChooserParams ): Boolean { openFileCallback2?.onReceiveValue(null) openFileCallback2 = filePathCallback if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val intent = fileChooserParams.createIntent() try { startActivityForResult(intent, REQUEST_FILE_CHOOSER) } catch (e: ActivityNotFoundException) { openFileCallback2 = null return false } } return true } } companion object { const val EVENT_LOGIN = "eventLogin" const val EVENT_TOKEN = "eventToken" const val KEY_TOKEN = "keyToken" private const val REQUEST_PERMISSIONS_LOCATION = 1 private const val REQUEST_FILE_CHOOSER = 1 } }