File indexing completed on 2025-02-02 04:47:49

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 package org.kde.kdeconnect.Helpers;
0008 
0009 import android.annotation.SuppressLint;
0010 import android.content.Context;
0011 import android.content.SharedPreferences;
0012 import android.content.res.Configuration;
0013 import android.content.res.Resources;
0014 import android.os.Build;
0015 import android.preference.PreferenceManager;
0016 import android.provider.Settings;
0017 import android.util.Log;
0018 
0019 import com.univocity.parsers.common.TextParsingException;
0020 import com.univocity.parsers.csv.CsvParser;
0021 import com.univocity.parsers.csv.CsvParserSettings;
0022 
0023 import org.kde.kdeconnect.DeviceInfo;
0024 import org.kde.kdeconnect.DeviceType;
0025 import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
0026 import org.kde.kdeconnect.Plugins.PluginFactory;
0027 
0028 import java.io.BufferedReader;
0029 import java.io.IOException;
0030 import java.io.InputStreamReader;
0031 import java.net.URL;
0032 import java.net.URLConnection;
0033 import java.nio.charset.StandardCharsets;
0034 import java.util.Set;
0035 import java.util.UUID;
0036 
0037 public class DeviceHelper {
0038 
0039     public static final int ProtocolVersion = 7;
0040 
0041     public static final String KEY_DEVICE_NAME_PREFERENCE = "device_name_preference";
0042     public static final String KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET = "device_name_downloaded_preference";
0043     public static final String KEY_DEVICE_ID_PREFERENCE = "device_id_preference";
0044 
0045     private static boolean fetchingName = false;
0046 
0047     public static final String DEVICE_DATABASE = "https://storage.googleapis.com/play_public/supported_devices.csv";
0048 
0049     private static boolean isTablet() {
0050         Configuration config = Resources.getSystem().getConfiguration();
0051         //This assumes that the values for the screen sizes are consecutive, so XXLARGE > XLARGE > LARGE
0052         return ((config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE);
0053     }
0054 
0055     private static boolean isTv(Context context) {
0056         int uiMode = context.getResources().getConfiguration().uiMode;
0057         return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
0058     }
0059 
0060     public static DeviceType getDeviceType(Context context) {
0061         if (isTv(context)) {
0062             return DeviceType.TV;
0063         } else if (isTablet()) {
0064             return DeviceType.TABLET;
0065         } else {
0066             return DeviceType.PHONE;
0067         }
0068     }
0069 
0070     public static String getDeviceName(Context context) {
0071         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
0072         if (!preferences.contains(KEY_DEVICE_NAME_PREFERENCE)
0073                 && !preferences.getBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, false)
0074                 && !fetchingName) {
0075             fetchingName = true;
0076             DeviceHelper.backgroundFetchDeviceName(context);
0077             return Build.MODEL;
0078         }
0079         return preferences.getString(KEY_DEVICE_NAME_PREFERENCE, Build.MODEL);
0080     }
0081 
0082     private static void backgroundFetchDeviceName(final Context context) {
0083         ThreadHelper.execute(() -> {
0084             try {
0085                 URL url = new URL(DEVICE_DATABASE);
0086                 URLConnection connection = url.openConnection();
0087 
0088                 // 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.
0089                 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
0090                 preferences.edit().putBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, true).apply();
0091 
0092                 try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_16))) {
0093                     CsvParserSettings settings = new CsvParserSettings();
0094                     settings.setHeaderExtractionEnabled(true);
0095                     CsvParser parser = new CsvParser(settings);
0096                     boolean found = false;
0097                     for (String[] records : parser.iterate(reader)) {
0098                         if (records.length < 4) {
0099                             continue;
0100                         }
0101                         String buildModel = records[3];
0102                         if (Build.MODEL.equalsIgnoreCase(buildModel)) {
0103                             String deviceName = records[1];
0104                             Log.i("DeviceHelper", "Got device name: " + deviceName);
0105                             // Update the shared preference. Places that display the name should be listening to this change and update it
0106                             setDeviceName(context, deviceName);
0107                             found = true;
0108                             break;
0109                         }
0110                     }
0111                     if (!found) {
0112                         Log.e("DeviceHelper", "Didn't find a device name for " + Build.MODEL);
0113                     }
0114                 }
0115             } catch(IOException | TextParsingException e) {
0116                 e.printStackTrace();
0117             }
0118             fetchingName = false;
0119         });
0120     }
0121 
0122     public static void setDeviceName(Context context, String name) {
0123         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
0124         preferences.edit().putString(KEY_DEVICE_NAME_PREFERENCE, name).apply();
0125     }
0126 
0127 
0128     @SuppressLint("HardwareIds")
0129     public static void initializeDeviceId(Context context) {
0130         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
0131         Set<String> preferenceKeys = preferences.getAll().keySet();
0132         if (preferenceKeys.contains(KEY_DEVICE_ID_PREFERENCE)) {
0133             return; // We already have an ID
0134         }
0135         String deviceName;
0136         if (preferenceKeys.isEmpty()) {
0137             // For new installations, use random IDs
0138             Log.i("DeviceHelper", "No device ID found and this looks like a new installation, creating a random ID");
0139             deviceName = UUID.randomUUID().toString().replace('-','_');
0140         } else {
0141             // Use the ANDROID_ID as device ID for existing installations, for backwards compatibility
0142             Log.i("DeviceHelper", "No device ID found but this seems an existing installation, using the Android ID");
0143             deviceName = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
0144         }
0145         preferences.edit().putString(KEY_DEVICE_ID_PREFERENCE, deviceName).apply();
0146     }
0147 
0148     public static String getDeviceId(Context context) {
0149         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
0150         return preferences.getString(KEY_DEVICE_ID_PREFERENCE, null);
0151     }
0152 
0153     public static DeviceInfo getDeviceInfo(Context context) {
0154         return new DeviceInfo(getDeviceId(context),
0155                 SslHelper.certificate,
0156                 getDeviceName(context),
0157                 DeviceHelper.getDeviceType(context),
0158                 ProtocolVersion,
0159                 PluginFactory.getIncomingCapabilities(),
0160                 PluginFactory.getOutgoingCapabilities());
0161     }
0162 
0163 }