Replace LogActivity with "Share Logs" + Add Timestamp to Logger

This commit is contained in:
PixelyIon 2021-06-21 21:30:15 +05:30 committed by ◱ Mark
parent e511e158e3
commit 6854e07356
16 changed files with 59 additions and 393 deletions

View File

@ -36,5 +36,10 @@
<option name="name" value="MavenLocal" />
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://google.bintray.com/flexbox-layout" />
</remote-repository>
</component>
</project>

View File

@ -1,5 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/layout/main_activity.xml" value="0.2028688524590164" />
<entry key="app/src/main/res/layout/settings_activity.xml" value="0.22302631578947368" />
<entry key="app/src/main/res/menu/toolbar_log.xml" value="0.11019736842105263" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY" value="$PROJECT_DIR$/../LightSwitchEXTRA/JDoc" />
@ -31,7 +40,7 @@
</option>
<option name="myNotNulls">
<value>
<list size="14">
<list size="15">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
@ -46,6 +55,7 @@
<item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
<item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
<item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
<item index="14" class="java.lang.String" itemvalue="lombok.NonNull" />
</list>
</value>
</option>

View File

@ -26,14 +26,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="emu.skyline.LogActivity"
android:label="@string/log"
android:parentActivityName="emu.skyline.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="emu.skyline.MainActivity" />
</activity>
<activity
android:name="emu.skyline.SettingsActivity"
android:exported="true"
@ -101,5 +93,15 @@
tools:ignore="AppLinkUrlError" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="skyline.emu.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>

View File

@ -11,14 +11,14 @@
#include "kernel/types/KThread.h"
namespace skyline {
Logger::Logger(const std::string &path, LogLevel configLevel) : configLevel(configLevel) {
Logger::Logger(const std::string &path, LogLevel configLevel) : configLevel(configLevel), start(util::GetTimeNs() / constant::NsInMillisecond) {
logFile.open(path, std::ios::trunc);
UpdateTag();
WriteHeader("Logging started");
Write(LogLevel::Info, "Logging started");
}
Logger::~Logger() {
WriteHeader("Logging ended");
Write(LogLevel::Info, "Logging ended");
logFile.flush();
}
@ -33,13 +33,6 @@ namespace skyline {
logTag = std::string("emu-cpp-") + threadName;
}
void Logger::WriteHeader(const std::string &str) {
__android_log_write(ANDROID_LOG_INFO, "emu-cpp", str.c_str());
std::lock_guard guard(mutex);
logFile << "\0360\035" << str << '\n';
}
void Logger::Write(LogLevel level, const std::string &str) {
constexpr std::array<char, 5> levelCharacter{'E', 'W', 'I', 'D', 'V'}; // The LogLevel as written out to a file
constexpr std::array<int, 5> levelAlog{ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, ANDROID_LOG_VERBOSE}; // This corresponds to LogLevel and provides its equivalent for NDK Logging
@ -50,7 +43,7 @@ namespace skyline {
__android_log_write(levelAlog[static_cast<u8>(level)], logTag.c_str(), str.c_str());
std::lock_guard guard(mutex);
logFile << "\0361\035" << levelCharacter[static_cast<u8>(level)] << '\035' << threadName << '\035' << str << '\n'; // We use RS (\036) and GS (\035) as our delimiters
logFile << '\036' << levelCharacter[static_cast<u8>(level)] << '\035' << std::dec << (util::GetTimeNs() / constant::NsInMillisecond) - start << '\035' << threadName << '\035' << str << '\n'; // We use RS (\036) and GS (\035) as our delimiters
}
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger)

View File

@ -122,6 +122,7 @@ namespace skyline {
namespace constant {
// Time
constexpr u64 NsInSecond{1000000000}; //!< The amount of nanoseconds in a second
constexpr u64 NsInMillisecond{1000000}; //!< The amount of nanoseconds in a millisecond
constexpr u64 NsInDay{86400000000000UL}; //!< The amount of nanoseconds in a day
}
@ -457,8 +458,9 @@ namespace skyline {
*/
class Logger {
private:
std::ofstream logFile; //!< An output stream to the log file
std::mutex mutex; //!< Synchronizes all output I/O to ensure there are no races
std::ofstream logFile; //!< An output stream to the log file
u64 start; //!< A timestamp in milliseconds for when the logger was started, this is used as the base for all log timestamps
public:
enum class LogLevel {
@ -487,11 +489,6 @@ namespace skyline {
*/
static void UpdateTag();
/**
* @brief Writes a header, should only be used for emulation starting and ending
*/
void WriteHeader(const std::string &str);
void Write(LogLevel level, const std::string &str);
/**

View File

@ -375,7 +375,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
is AxisGuestEvent -> {
value = guestEvent.value(value)
value = if (polarity) abs(value) else -abs(value)
value = if (guestEvent.axis == AxisId.LX || guestEvent.axis == AxisId.RX) value else -value // TODO: Test this
value = if (guestEvent.axis == AxisId.LX || guestEvent.axis == AxisId.RX) value else -value
setAxisValue(guestEvent.id, guestEvent.axis.ordinal, (value * Short.MAX_VALUE).toInt())
}

View File

@ -1,204 +0,0 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.adapter.GenericAdapter
import emu.skyline.adapter.HeaderViewItem
import emu.skyline.adapter.LogViewItem
import emu.skyline.databinding.LogActivityBinding
import emu.skyline.utils.Settings
import org.json.JSONObject
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.net.URL
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@AndroidEntryPoint
class LogActivity : AppCompatActivity() {
private val binding by lazy { LogActivityBinding.inflate(layoutInflater) }
/**
* The log file is used to read log entries from or to clear all entries
*/
private lateinit var logFile : File
private val adapter = GenericAdapter()
@Inject
lateinit var settings : Settings
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setSupportActionBar(binding.titlebar.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val compact = settings.logCompact
val logLevel = settings.logLevel.toInt()
val logLevels = resources.getStringArray(R.array.log_level)
binding.logList.adapter = adapter
if (!compact) binding.logList.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
try {
logFile = File(applicationContext.filesDir.canonicalPath + "/skyline.log")
adapter.setItems(logFile.readLines().mapNotNull { logLine ->
try {
val logMeta = logLine.split("|", limit = 3)
if (logMeta[0].startsWith("1")) {
val level = logMeta[1].toInt()
if (level > logLevel) return@mapNotNull null
return@mapNotNull LogViewItem(compact, "(" + logMeta[2] + ") " + logMeta[3].replace('\\', '\n'), logLevels[level])
} else {
return@mapNotNull HeaderViewItem(logMeta[1])
}
} catch (ignored : IndexOutOfBoundsException) {
} catch (ignored : NumberFormatException) {
}
null
})
} catch (e : FileNotFoundException) {
Log.w("Logger", "IO Error during access of log file: " + e.message)
Toast.makeText(applicationContext, getString(R.string.file_missing), Toast.LENGTH_LONG).show()
finish()
} catch (e : IOException) {
Log.w("Logger", "IO Error during access of log file: " + e.message)
Toast.makeText(applicationContext, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show()
}
}
/**
* This inflates the layout for the menu [R.menu.toolbar_log] and sets up searching the logs
*/
override fun onCreateOptionsMenu(menu : Menu) : Boolean {
menuInflater.inflate(R.menu.toolbar_log, menu)
val searchView = menu.findItem(R.id.action_search_log).actionView as SearchView
searchView.isSubmitButtonEnabled = false
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query : String) : Boolean {
searchView.isIconified = false
return false
}
override fun onQueryTextChange(newText : String) : Boolean {
adapter.filter.filter(newText)
return true
}
})
return super.onCreateOptionsMenu(menu)
}
/**
* This handles menu selection for [R.id.action_clear] and [R.id.action_share_log]
*/
override fun onOptionsItemSelected(item : MenuItem) : Boolean {
return when (item.itemId) {
R.id.action_clear -> {
try {
logFile.writeText("")
} catch (e : IOException) {
Log.w("Logger", "IO Error while clearing the log file: " + e.message)
Toast.makeText(applicationContext, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show()
}
Toast.makeText(applicationContext, getString(R.string.cleared), Toast.LENGTH_LONG).show()
finish()
true
}
R.id.action_share_log -> {
uploadAndShareLog()
true
}
else -> super.onOptionsItemSelected(item)
}
}
/**
* This uploads the logs and launches the [Intent.ACTION_SEND] intent
*/
private fun uploadAndShareLog() {
Snackbar.make(findViewById(android.R.id.content), getString(R.string.upload_logs), Snackbar.LENGTH_SHORT).show()
val shareThread = Thread {
var urlConnection : HttpsURLConnection? = null
try {
val url = URL("https://hastebin.com/documents")
urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.requestMethod = "POST"
urlConnection.setRequestProperty("Host", "hastebin.com")
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
urlConnection.setRequestProperty("Referer", "https://hastebin.com/")
urlConnection.outputStream.bufferedWriter().use {
it.write(logFile.readText())
it.flush()
}
if (urlConnection.responseCode != 200) {
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.responseCode)
throw Exception()
}
val bufferedReader = urlConnection.inputStream.bufferedReader()
val key = JSONObject(bufferedReader.readText()).getString("key")
bufferedReader.close()
val result = "https://hastebin.com/$key"
val sharingIntent = Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result)
startActivity(Intent.createChooser(sharingIntent, "Share log url with:"))
} catch (e : Exception) {
runOnUiThread { Snackbar.make(findViewById(android.R.id.content), getString(R.string.error) + ": ${e.localizedMessage}", Snackbar.LENGTH_LONG).show() }
e.printStackTrace()
} finally {
urlConnection!!.disconnect()
}
}
shareThread.start()
}
/**
* This handles on calling [onBackPressed] when [KeyEvent.KEYCODE_BUTTON_B] is lifted
*/
override fun onKeyUp(keyCode : Int, event : KeyEvent?) : Boolean {
if (keyCode == KeyEvent.KEYCODE_BUTTON_B) {
onBackPressed()
return true
}
return super.onKeyUp(keyCode, event)
}
}

View File

@ -16,6 +16,7 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.res.use
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isInvisible
@ -35,6 +36,7 @@ import emu.skyline.loader.AppEntry
import emu.skyline.loader.LoaderResult
import emu.skyline.loader.RomFormat
import emu.skyline.utils.Settings
import emu.skyline.utils.toFile
import javax.inject.Inject
import kotlin.math.ceil
import kotlin.math.roundToInt
@ -120,7 +122,7 @@ class MainActivity : AppCompatActivity() {
binding.swipeRefreshLayout.apply {
setProgressBackgroundColorSchemeColor(obtainStyledAttributes(intArrayOf(R.attr.colorPrimary)).use { it.getColor(0, Color.BLACK) })
setColorSchemeColors(obtainStyledAttributes(intArrayOf(R.attr.colorAccent)).use { it.getColor(0, Color.BLACK) })
post { setDistanceToTriggerSync((binding.swipeRefreshLayout.height / 2.5f).roundToInt()) }
post { setDistanceToTriggerSync(binding.swipeRefreshLayout.height / 3) }
setOnRefreshListener { loadRoms(false) }
}
@ -128,7 +130,17 @@ class MainActivity : AppCompatActivity() {
loadRoms(!settings.refreshRequired)
binding.searchBar.apply {
binding.logIcon.setOnClickListener { startActivity(Intent(context, LogActivity::class.java)) }
binding.logIcon.setOnClickListener {
val file = applicationContext.filesDir.resolve("skyline.log")
if (file.length() != 0L) {
val intent = Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(this@MainActivity, "skyline.emu.fileprovider", file))
startActivity(Intent.createChooser(intent, getString(R.string.log_share_prompt)))
} else {
Snackbar.make(this@MainActivity.findViewById(android.R.id.content), getString(R.string.logs_not_found), Snackbar.LENGTH_SHORT).show()
}
}
binding.settingsIcon.setOnClickListener { settingsCallback.launch(Intent(context, SettingsActivity::class.java)) }
binding.refreshIcon.setOnClickListener { loadRoms(false) }
addTextChangedListener(afterTextChanged = { editable ->

View File

@ -1,59 +0,0 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.adapter
import android.content.ClipData
import android.content.ClipboardManager
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.viewbinding.ViewBinding
import emu.skyline.databinding.LogItemBinding
import emu.skyline.databinding.LogItemCompactBinding
data class LogBindingFactory(private val compact : Boolean) : ViewBindingFactory {
override fun createBinding(parent : ViewGroup) = if (compact) LogCompactBinding(parent) else LogBinding(parent)
}
interface ILogBinding : ViewBinding {
val binding : ViewBinding
override fun getRoot() = binding.root
val textTitle : TextView
val textSubTitle : TextView?
}
class LogCompactBinding(parent : ViewGroup) : ILogBinding {
override val binding = LogItemCompactBinding.inflate(parent.inflater(), parent, false)
override val textTitle = binding.textTitle
override val textSubTitle : Nothing? = null
}
class LogBinding(parent : ViewGroup) : ILogBinding {
override val binding = LogItemBinding.inflate(parent.inflater(), parent, false)
override val textTitle = binding.textTitle
override val textSubTitle = binding.textSubtitle
}
data class LogViewItem(private val compact : Boolean, private val message : String, private val level : String) : GenericListItem<ILogBinding>() {
override fun getViewBindingFactory() = LogBindingFactory(compact)
override fun bind(binding : ILogBinding, position : Int) {
binding.textTitle.text = message
binding.textSubTitle?.text = level
binding.root.setOnClickListener {
it.context.getSystemService(ClipboardManager::class.java).setPrimaryClip(ClipData.newPlainText("Log Message", "$message ($level)"))
Toast.makeText(it.context, "Copied to clipboard", Toast.LENGTH_LONG).show()
}
}
}

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LogActivity">
<include
android:id="@+id/titlebar"
layout="@layout/titlebar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/log_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fastScrollEnabled="true"
android:focusedByDefault="true"
android:transcriptMode="normal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:padding="15dp">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="5dp"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textSize="15sp" />
<TextView
android:id="@+id/text_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:padding="1dp">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -53,7 +53,7 @@
android:layout_gravity="center_vertical"
android:layout_marginEnd="4dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/log"
android:contentDescription="@string/share_logs"
android:padding="5dp"
app:layout_constraintBottom_toBottomOf="@id/sub_text"
app:layout_constraintEnd_toStartOf="@id/settings_icon"

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search_log"
android:icon="@drawable/ic_search"
android:title="@string/search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:iconTint="?android:attr/textColorSecondary"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_share_log"
android:icon="@drawable/ic_share"
android:title="@string/share"
app:iconTint="?android:attr/textColorSecondary"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_clear"
android:icon="@drawable/ic_clear"
android:title="@string/clear"
app:iconTint="?android:attr/textColorSecondary"
app:showAsAction="ifRoom" />
</menu>

View File

@ -5,8 +5,11 @@
<string name="error">An error has occurred</string>
<!-- Toolbar Main -->
<string name="settings">Settings</string>
<string name="log">Logger</string>
<string name="share_logs">Share Logs</string>
<string name="refresh">Refresh</string>
<!-- Toolbar - Share Logs -->
<string name="log_share_prompt">Share log file</string>
<string name="logs_not_found">No logs were created during the last run</string>
<!-- Main -->
<string name="all">All</string>
<string name="metadata_missing">Metadata Missing</string>
@ -18,13 +21,6 @@
<string name="invalid_file">Invalid file</string>
<string name="missing_title_key">Missing title key</string>
<string name="incomplete_prod_keys">Incomplete production keys</string>
<!-- Toolbar Logger -->
<string name="clear">Clear</string>
<string name="share">Share</string>
<!-- Logger -->
<string name="file_missing">The log file was not found</string>
<string name="upload_logs">The logs are being uploaded</string>
<string name="cleared">The logs have been cleared</string>
<!-- Settings - Emulator -->
<string name="emulator">Emulator</string>
<string name="search_location">Search Location</string>
@ -56,8 +52,8 @@
<!-- Settings - Display -->
<string name="display">Display</string>
<string name="force_triple_buffering">Force Triple Buffering</string>
<string name="triple_buffering_enabled">Utilize at least 3 swapchain buffers (Higher FPS with higher input lag)</string>
<string name="triple_buffering_disabled">Utilize at least 2 swapchain buffers (Lower FPS with lower input lag)</string>
<string name="triple_buffering_enabled">Utilize at least three swapchain buffers (Higher FPS but more input lag)</string>
<string name="triple_buffering_disabled">Utilize at least two swapchain buffers (Lower FPS but less input lag)</string>
<string name="disable_frame_throttling">Disable Frame Throttling</string>
<string name="disable_frame_throttling_enabled">Game is allowed to submit frames as fast as possible (Only for benchmarking)</string>
<string name="disable_frame_throttling_disabled">Only allow the game to submit frames at the display refresh rate</string>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path path="/" name="Skyline" />
</paths>