diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java b/src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java index 42d77bdd..70151251 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java @@ -131,7 +131,7 @@ public class MdnsDiscovery { // Each field (key + value) needs to be < 255 bytes. All the fields combined need to be < 1300 bytes. // Also, on Android Lollipop those fields aren't resolved. String deviceName = DeviceHelper.getDeviceName(context); - String deviceType = DeviceHelper.getDeviceType(context).toString(); + String deviceType = DeviceHelper.getDeviceType().toString(); String protocolVersion = Integer.toString(DeviceHelper.ProtocolVersion); serviceInfo.setAttribute("id", deviceId); serviceInfo.setAttribute("name", deviceName); diff --git a/src/org/kde/kdeconnect/Helpers/DeviceHelper.java b/src/org/kde/kdeconnect/Helpers/DeviceHelper.java deleted file mode 100644 index 37c6bb03..00000000 --- a/src/org/kde/kdeconnect/Helpers/DeviceHelper.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora - * - * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -*/ - -package org.kde.kdeconnect.Helpers; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Build; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.util.Log; - -import com.univocity.parsers.common.TextParsingException; -import com.univocity.parsers.csv.CsvParser; -import com.univocity.parsers.csv.CsvParserSettings; - -import org.kde.kdeconnect.DeviceInfo; -import org.kde.kdeconnect.DeviceType; -import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; -import org.kde.kdeconnect.Plugins.PluginFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.net.URLConnection; -import java.nio.charset.StandardCharsets; -import java.util.Set; -import java.util.UUID; - -public class DeviceHelper { - - public static final int ProtocolVersion = 7; - - public static final String KEY_DEVICE_NAME_PREFERENCE = "device_name_preference"; - public static final String KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET = "device_name_downloaded_preference"; - public static final String KEY_DEVICE_ID_PREFERENCE = "device_id_preference"; - - private static boolean fetchingName = false; - - public static final String DEVICE_DATABASE = "https://storage.googleapis.com/play_public/supported_devices.csv"; - - private static boolean isTablet() { - Configuration config = Resources.getSystem().getConfiguration(); - //This assumes that the values for the screen sizes are consecutive, so XXLARGE > XLARGE > LARGE - return ((config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE); - } - - private static boolean isTv(Context context) { - int uiMode = context.getResources().getConfiguration().uiMode; - return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION; - } - - public static DeviceType getDeviceType(Context context) { - if (isTv(context)) { - return DeviceType.TV; - } else if (isTablet()) { - return DeviceType.TABLET; - } else { - return DeviceType.PHONE; - } - } - - public static String getDeviceName(Context context) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - if (!preferences.contains(KEY_DEVICE_NAME_PREFERENCE) - && !preferences.getBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, false) - && !fetchingName) { - fetchingName = true; - DeviceHelper.backgroundFetchDeviceName(context); - return Build.MODEL; - } - return preferences.getString(KEY_DEVICE_NAME_PREFERENCE, Build.MODEL); - } - - private static void backgroundFetchDeviceName(final Context context) { - ThreadHelper.execute(() -> { - try { - URL url = new URL(DEVICE_DATABASE); - URLConnection connection = url.openConnection(); - - // If we get here we managed to download the file. Mark that as done so we don't try again even if we don't end up finding a name. - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - preferences.edit().putBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, true).apply(); - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_16))) { - CsvParserSettings settings = new CsvParserSettings(); - settings.setHeaderExtractionEnabled(true); - CsvParser parser = new CsvParser(settings); - boolean found = false; - for (String[] records : parser.iterate(reader)) { - if (records.length < 4) { - continue; - } - String buildModel = records[3]; - if (Build.MODEL.equalsIgnoreCase(buildModel)) { - String deviceName = records[1]; - Log.i("DeviceHelper", "Got device name: " + deviceName); - // Update the shared preference. Places that display the name should be listening to this change and update it - setDeviceName(context, deviceName); - found = true; - break; - } - } - if (!found) { - Log.e("DeviceHelper", "Didn't find a device name for " + Build.MODEL); - } - } - } catch(IOException | TextParsingException e) { - e.printStackTrace(); - } - fetchingName = false; - }); - } - - public static void setDeviceName(Context context, String name) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - preferences.edit().putString(KEY_DEVICE_NAME_PREFERENCE, name).apply(); - } - - - @SuppressLint("HardwareIds") - public static void initializeDeviceId(Context context) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - Set preferenceKeys = preferences.getAll().keySet(); - if (preferenceKeys.contains(KEY_DEVICE_ID_PREFERENCE)) { - return; // We already have an ID - } - String deviceName; - if (preferenceKeys.isEmpty()) { - // For new installations, use random IDs - Log.i("DeviceHelper", "No device ID found and this looks like a new installation, creating a random ID"); - deviceName = UUID.randomUUID().toString().replace('-','_'); - } else { - // Use the ANDROID_ID as device ID for existing installations, for backwards compatibility - Log.i("DeviceHelper", "No device ID found but this seems an existing installation, using the Android ID"); - deviceName = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); - } - preferences.edit().putString(KEY_DEVICE_ID_PREFERENCE, deviceName).apply(); - } - - public static String getDeviceId(Context context) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - return preferences.getString(KEY_DEVICE_ID_PREFERENCE, null); - } - - public static DeviceInfo getDeviceInfo(Context context) { - return new DeviceInfo(getDeviceId(context), - SslHelper.certificate, - getDeviceName(context), - DeviceHelper.getDeviceType(context), - ProtocolVersion, - PluginFactory.getIncomingCapabilities(), - PluginFactory.getOutgoingCapabilities()); - } - -} diff --git a/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt b/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt new file mode 100644 index 00000000..83ad76ce --- /dev/null +++ b/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt @@ -0,0 +1,163 @@ +/* + * SPDX-FileCopyrightText: 2024 Albert Vaca Cintora + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ +package org.kde.kdeconnect.Helpers + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Build +import android.preference.PreferenceManager +import android.provider.Settings +import android.util.Log +import com.univocity.parsers.common.TextParsingException +import com.univocity.parsers.csv.CsvParser +import com.univocity.parsers.csv.CsvParserSettings +import org.kde.kdeconnect.DeviceInfo +import org.kde.kdeconnect.DeviceType +import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper +import org.kde.kdeconnect.Plugins.PluginFactory +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.net.URL +import java.nio.charset.StandardCharsets +import java.util.UUID + +object DeviceHelper { + const val ProtocolVersion = 7 + + const val KEY_DEVICE_NAME_PREFERENCE = "device_name_preference" + private const val KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET = "device_name_downloaded_preference" + private const val KEY_DEVICE_ID_PREFERENCE = "device_id_preference" + + private var fetchingName = false + + private const val DEVICE_DATABASE = "https://storage.googleapis.com/play_public/supported_devices.csv" + + private val isTablet: Boolean by lazy { + val config = Resources.getSystem().configuration + //This assumes that the values for the screen sizes are consecutive, so XXLARGE > XLARGE > LARGE + ((config.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE) + } + + private val isTv: Boolean by lazy { + val uiMode = Resources.getSystem().configuration.uiMode + (uiMode and Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION + } + + @JvmStatic + val deviceType: DeviceType by lazy { + if (isTv) { + DeviceType.TV + } else if (isTablet) { + DeviceType.TABLET + } else { + DeviceType.PHONE + } + } + + @JvmStatic + fun getDeviceName(context: Context): String { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + if (!preferences.contains(KEY_DEVICE_NAME_PREFERENCE) + && !preferences.getBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, false) + && !fetchingName + ) { + fetchingName = true + backgroundFetchDeviceName(context) + return Build.MODEL + } + return preferences.getString(KEY_DEVICE_NAME_PREFERENCE, Build.MODEL)!! + } + + private fun backgroundFetchDeviceName(context: Context) { + ThreadHelper.execute { + try { + val url = URL(DEVICE_DATABASE) + val connection = url.openConnection() + + // If we get here we managed to download the file. Mark that as done so we don't try again even if we don't end up finding a name. + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + preferences.edit().putBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, true).apply() + + BufferedReader( + InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_16) + ).use { reader -> + val settings = CsvParserSettings() + settings.isHeaderExtractionEnabled = true + val parser = CsvParser(settings) + var found = false + for (records in parser.iterate(reader)) { + if (records.size < 4) { + continue + } + val buildModel = records[3] + if (Build.MODEL.equals(buildModel, ignoreCase = true)) { + val deviceName = records[1] + Log.i("DeviceHelper", "Got device name: $deviceName") + // Update the shared preference. Places that display the name should be listening to this change and update it + setDeviceName(context, deviceName) + found = true + break + } + } + if (!found) { + Log.e("DeviceHelper", "Didn't find a device name for " + Build.MODEL) + } + } + } catch (e: IOException) { + e.printStackTrace() + } catch (e: TextParsingException) { + e.printStackTrace() + } + fetchingName = false + } + } + + fun setDeviceName(context: Context, name: String) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + preferences.edit().putString(KEY_DEVICE_NAME_PREFERENCE, name).apply() + } + + fun initializeDeviceId(context: Context) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val preferenceKeys: Set = preferences.all.keys + if (preferenceKeys.contains(KEY_DEVICE_ID_PREFERENCE)) { + return // We already have an ID + } + @SuppressLint("HardwareIds") + val deviceName = if (preferenceKeys.isEmpty()) { + // For new installations, use random IDs + Log.i("DeviceHelper","No device ID found and this looks like a new installation, creating a random ID") + UUID.randomUUID().toString().replace('-', '_') + } else { + // Use the ANDROID_ID as device ID for existing installations, for backwards compatibility + Log.i("DeviceHelper", "No device ID found but this seems an existing installation, using the Android ID") + Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) + } + preferences.edit().putString(KEY_DEVICE_ID_PREFERENCE, deviceName).apply() + } + + @JvmStatic + fun getDeviceId(context: Context): String { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + return preferences.getString(KEY_DEVICE_ID_PREFERENCE, null)!! + } + + @JvmStatic + fun getDeviceInfo(context: Context): DeviceInfo { + return DeviceInfo( + getDeviceId(context), + SslHelper.certificate, + getDeviceName(context), + deviceType, + ProtocolVersion, + PluginFactory.getIncomingCapabilities(), + PluginFactory.getOutgoingCapabilities() + ) + } +} diff --git a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java index 73a9652a..cfec36fc 100644 --- a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java +++ b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java @@ -52,7 +52,7 @@ public class FindMyPhonePlugin extends Plugin { @Override public @NonNull String getDisplayName() { - switch (DeviceHelper.getDeviceType(context)) { + switch (DeviceHelper.getDeviceType()) { case TV: return context.getString(R.string.findmyphone_title_tv); case TABLET: diff --git a/src/org/kde/kdeconnect/UserInterface/MainActivity.kt b/src/org/kde/kdeconnect/UserInterface/MainActivity.kt index 19edaddd..5037ddea 100644 --- a/src/org/kde/kdeconnect/UserInterface/MainActivity.kt +++ b/src/org/kde/kdeconnect/UserInterface/MainActivity.kt @@ -108,7 +108,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener { // it can trigger a background fetch from the internet that will eventually update the preference PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this) val deviceName = DeviceHelper.getDeviceName(this) - mNavViewDeviceType?.setImageDrawable(DeviceHelper.getDeviceType(this).getIcon(this)) + mNavViewDeviceType?.setImageDrawable(DeviceHelper.deviceType.getIcon(this)) mNavViewDeviceName.text = deviceName mNavigationView.setNavigationItemSelectedListener { menuItem: MenuItem -> mCurrentMenuEntry = menuItem.itemId