diff --git a/database/android-devices.db b/database/android-devices.db index 15eed91a8..6e6258955 100644 Binary files a/database/android-devices.db and b/database/android-devices.db differ diff --git a/database/android-devices.zip b/database/android-devices.zip index f5f9b1f6f..8c9ef3dbf 100644 Binary files a/database/android-devices.zip and b/database/android-devices.zip differ diff --git a/generator/src/main/kotlin/com/jaredrummler/androiddevicenames/DatabaseGenerator.kt b/generator/src/main/kotlin/com/jaredrummler/androiddevicenames/DatabaseGenerator.kt index 5e9b17520..a31a720f9 100644 --- a/generator/src/main/kotlin/com/jaredrummler/androiddevicenames/DatabaseGenerator.kt +++ b/generator/src/main/kotlin/com/jaredrummler/androiddevicenames/DatabaseGenerator.kt @@ -40,12 +40,15 @@ class DatabaseGenerator( DriverManager.getConnection(url)?.let { conn -> conn.createStatement().execute(SQL_DROP) + conn.createStatement().execute(SQL_ANDROID_METADATA_TABLE_DROP) conn.createStatement().execute(SQL_CREATE) + conn.createStatement().execute(SQL_ANDROID_METADATA_TABLE) val statement = conn.prepareStatement(SQL_INSERT) devices.forEach { device -> - statement.setString(1, device.marketName) - statement.setString(2, device.codename) - statement.setString(3, device.model) + statement.setString(1, device.manufacturer) + statement.setString(2, device.marketName) + statement.setString(3, device.codename) + statement.setString(4, device.model) statement.addBatch() } statement.executeBatch() @@ -68,13 +71,16 @@ class DatabaseGenerator( companion object { private const val SQL_INSERT = - "INSERT INTO devices (name, codename, model) VALUES (?, ?, ?)" + "INSERT INTO devices (manufacturer, name, codename, model) VALUES (? ,?, ?, ?)" private const val SQL_DROP = "DROP TABLE IF EXISTS devices;" + private const val SQL_ANDROID_METADATA_TABLE_DROP = "DROP TABLE IF EXISTS android_metadata;" private const val SQL_CREATE = "CREATE TABLE devices (\n" + "_id INTEGER PRIMARY KEY,\n" + + "manufacturer TEXT,\n" + "name TEXT,\n" + "codename TEXT,\n" + "model TEXT\n" + ");" + private const val SQL_ANDROID_METADATA_TABLE = "CREATE TABLE \"android_metadata\" (\"locale\" TEXT DEFAULT 'en_US');" } } \ No newline at end of file diff --git a/library/src/main/assets/android-devices.db b/library/src/main/assets/android-devices.db index 15eed91a8..6e6258955 100644 Binary files a/library/src/main/assets/android-devices.db and b/library/src/main/assets/android-devices.db differ diff --git a/library/src/main/java/com/jaredrummler/android/device/DeviceDatabase.java b/library/src/main/java/com/jaredrummler/android/device/DeviceDatabase.java index d51b1d6ae..143695138 100644 --- a/library/src/main/java/com/jaredrummler/android/device/DeviceDatabase.java +++ b/library/src/main/java/com/jaredrummler/android/device/DeviceDatabase.java @@ -24,12 +24,13 @@ public class DeviceDatabase extends SQLiteOpenHelper { private static final String TABLE_DEVICES = "devices"; + private static final String COLUMN_MANUFACTURER = "manufacturer"; private static final String COLUMN_NAME = "name"; private static final String COLUMN_CODENAME = "codename"; private static final String COLUMN_MODEL = "model"; private static final String NAME = "android-devices.db"; - private static final int VERSION = 1; + private static final int VERSION = 2; private final File file; private final Context context; @@ -58,7 +59,7 @@ public String query(@Nullable String codename, @Nullable String model) { String selection; String[] selectionArgs; if (codename != null && model != null) { - selection = COLUMN_CODENAME + " LIKE ? OR " + COLUMN_MODEL + " LIKE ?"; + selection = COLUMN_CODENAME + " LIKE ? AND " + COLUMN_MODEL + " LIKE ?"; selectionArgs = new String[] { codename, model }; } else if (codename != null) { selection = COLUMN_CODENAME + " LIKE ?"; @@ -95,17 +96,17 @@ public String query(@Nullable String codename, @Nullable String model) { public DeviceInfo queryToDevice(@Nullable String codename, @Nullable String model) { SQLiteDatabase database = getReadableDatabase(); - String[] columns = new String[] { COLUMN_NAME, COLUMN_CODENAME, COLUMN_MODEL }; + String[] columns = new String[] { COLUMN_MANUFACTURER ,COLUMN_NAME, COLUMN_CODENAME, COLUMN_MODEL }; String selection; String[] selectionArgs; if (!TextUtils.isEmpty(codename) && !TextUtils.isEmpty(model)) { - selection = COLUMN_CODENAME + " LIKE ? OR " + COLUMN_MODEL + " LIKE ?"; + selection = COLUMN_CODENAME + " LIKE ? AND " + COLUMN_MODEL + " LIKE ?"; selectionArgs = new String[] { codename, model }; } else if (!TextUtils.isEmpty(codename)) { selection = COLUMN_CODENAME + " LIKE ?"; selectionArgs = new String[] { codename }; - } else if (TextUtils.isEmpty(model)) { + } else if (!TextUtils.isEmpty(model)) { selection = COLUMN_MODEL + " LIKE ?"; selectionArgs = new String[] { model }; } else { @@ -118,10 +119,11 @@ public DeviceInfo queryToDevice(@Nullable String codename, @Nullable String mode DeviceInfo deviceInfo = null; if (cursor.moveToFirst()) { + String manufacturer = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MANUFACTURER)); String name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)); codename = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_CODENAME)); model = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MODEL)); - deviceInfo = new DeviceInfo(name, codename, model); + deviceInfo = new DeviceInfo(manufacturer,name, codename, model); } close(cursor); diff --git a/library/src/main/java/com/jaredrummler/android/device/DeviceName.java b/library/src/main/java/com/jaredrummler/android/device/DeviceName.java index 9436415c4..367cf9f60 100644 --- a/library/src/main/java/com/jaredrummler/android/device/DeviceName.java +++ b/library/src/main/java/com/jaredrummler/android/device/DeviceName.java @@ -24,7 +24,9 @@ import android.os.Handler; import android.os.Looper; import android.text.TextUtils; + import androidx.annotation.WorkerThread; + import org.json.JSONException; import org.json.JSONObject; @@ -78,336 +80,372 @@ */ public class DeviceName { - // Preference filename for storing device info so we don't need to download it again. - private static final String SHARED_PREF_NAME = "device_names"; - - @SuppressLint("StaticFieldLeak") // application context is safe - private static Context context; - - /** - * Initialize DeviceName. This should be done in the application class. - */ - public static void init(Context context) { - DeviceName.context = context.getApplicationContext(); - } - - /** - * Create a new request to get information about a device. - * - * @param context the application context - * @return a new Request instance. - */ - public static Request with(Context context) { - return new Request(context.getApplicationContext()); - } - - /** - * Get the consumer friendly name of the device. - * - * @return the market name of the current device. - * @see #getDeviceName(String, String) - */ - public static String getDeviceName() { - return getDeviceName(Build.DEVICE, Build.MODEL, capitalize(Build.MODEL)); - } - - /** - * Get the consumer friendly name of a device. - * - * @param codename the value of the system property "ro.product.device" ({@link Build#DEVICE}) - * or - * the value of the system property "ro.product.model" ({@link Build#MODEL}) - * @param fallback the fallback name if the device is unknown. Usually the value of the system - * property "ro.product.model" ({@link Build#MODEL}) - * @return the market name of a device or {@code fallback} if the device is unknown. - */ - public static String getDeviceName(String codename, String fallback) { - return getDeviceName(codename, codename, fallback); - } - - /** - * Get the consumer friendly name of a device. - * - * @param codename the value of the system property "ro.product.device" ({@link Build#DEVICE}). - * @param model the value of the system property "ro.product.model" ({@link Build#MODEL}). - * @param fallback the fallback name if the device is unknown. Usually the value of the system - * property "ro.product.model" ({@link Build#MODEL}) - * @return the market name of a device or {@code fallback} if the device is unknown. - */ - public static String getDeviceName(String codename, String model, String fallback) { - String marketName = getDeviceInfo(context(), codename, model).marketName; - return marketName == null ? fallback : marketName; - } - - /** - * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may - * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then - * stored to {@link SharedPreferences}. - * - * @param context the application context. - * @return {@link DeviceInfo} for the current device. - */ - @WorkerThread - public static DeviceInfo getDeviceInfo(Context context) { - return getDeviceInfo(context.getApplicationContext(), Build.DEVICE, Build.MODEL); - } - - /** - * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may - * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then - * stored to {@link SharedPreferences}. - * - * @param context the application context. - * @param codename the codename of the device - * @return {@link DeviceInfo} for the current device. - */ - @WorkerThread - public static DeviceInfo getDeviceInfo(Context context, String codename) { - return getDeviceInfo(context, codename, null); - } - - /** - * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may - * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then - * stored to {@link SharedPreferences}. - * - * @param context the application context. - * @param codename the codename of the device - * @param model the model of the device - * @return {@link DeviceInfo} for the current device. - */ - @WorkerThread - public static DeviceInfo getDeviceInfo(Context context, String codename, String model) { - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); - String key = String.format("%s:%s", codename, model); - String savedJson = prefs.getString(key, null); - if (savedJson != null) { - try { - return new DeviceInfo(new JSONObject(savedJson)); - } catch (JSONException e) { - e.printStackTrace(); - } - } + // Preference filename for storing device info so we don't need to download it again. + private static final String SHARED_PREF_NAME = "device_names"; - try (DeviceDatabase database = new DeviceDatabase(context)) { - DeviceInfo info = database.queryToDevice(codename, model); - if (info != null) { - JSONObject json = new JSONObject(); - json.put("manufacturer", info.manufacturer); - json.put("codename", info.codename); - json.put("model", info.model); - json.put("market_name", info.marketName); - - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(key, json.toString()); - editor.apply(); - return info; - } - } catch (Exception e) { - e.printStackTrace(); - } + @SuppressLint("StaticFieldLeak") // application context is safe + private static Context context; - if (codename.equals(Build.DEVICE) && Build.MODEL.equals(model)) { - return new DeviceInfo(Build.MANUFACTURER, codename, codename, model); // current device + /** + * Initialize DeviceName. This should be done in the application class. + */ + public static void init(Context context) { + DeviceName.context = context.getApplicationContext(); } - return new DeviceInfo(null, null, codename, model); // unknown device - } - - /** - *
Capitalizes getAllProcesses the whitespace separated words in a String. Only the first - * letter of each word is changed.
- * - * Whitespace is defined by {@link Character#isWhitespace(char)}. - * - * @param str the String to capitalize - * @return capitalized The capitalized String - */ - private static String capitalize(String str) { - if (TextUtils.isEmpty(str)) { - return str; - } - char[] arr = str.toCharArray(); - boolean capitalizeNext = true; - StringBuilder phrase = new StringBuilder(); - for (char c : arr) { - if (capitalizeNext && Character.isLetter(c)) { - phrase.append(Character.toUpperCase(c)); - capitalizeNext = false; - continue; - } else if (Character.isWhitespace(c)) { - capitalizeNext = true; - } - phrase.append(c); + /** + * Create a new request to get information about a device. + * + * @param context the application context + * @return a new Request instance. + */ + public static Request with(Context context) { + return new Request(context.getApplicationContext()); } - return phrase.toString(); - } - - public static final class Request { - final Context context; - final Handler handler; - String codename; - String model; - - private Request(Context ctx) { - context = ctx; - handler = new Handler(ctx.getMainLooper()); + /** + * Get the consumer friendly name of the device. + * + * @return the market name of the current device. + * @see #getDeviceName(String, String) + */ + public static String getDeviceName() { + return getDeviceName(Build.DEVICE, Build.MODEL, capitalize(Build.MODEL)); } /** - * Set the device codename to query. You should also set the model. + * Get the consumer friendly name of a device. * - * @param codename the value of the system property "ro.product.device" - * @return This Request object to allow for chaining of calls to set methods. - * @see Build#DEVICE + * @param codename the value of the system property "ro.product.device" ({@link Build#DEVICE}) + * or + * the value of the system property "ro.product.model" ({@link Build#MODEL}) + * @param fallback the fallback name if the device is unknown. Usually the value of the system + * property "ro.product.model" ({@link Build#MODEL}) + * @return the market name of a device or {@code fallback} if the device is unknown. */ - public Request setCodename(String codename) { - this.codename = codename; - return this; + public static String getDeviceName(String codename, String fallback) { + return getDeviceName(codename, codename, fallback); } /** - * Set the device model to query. You should also set the codename. + * Get the consumer friendly name of a device. * - * @param model the value of the system property "ro.product.model" - * @return This Request object to allow for chaining of calls to set methods. - * @see Build#MODEL + * @param codename the value of the system property "ro.product.device" ({@link Build#DEVICE}). + * @param model the value of the system property "ro.product.model" ({@link Build#MODEL}). + * @param fallback the fallback name if the device is unknown. Usually the value of the system + * property "ro.product.model" ({@link Build#MODEL}) + * @return the market name of a device or {@code fallback} if the device is unknown. */ - public Request setModel(String model) { - this.model = model; - return this; + public static String getDeviceName(String codename, String model, String fallback) { + String marketName = getDeviceInfo(context(), codename, model).marketName; + return marketName == null ? fallback : marketName; } /** - * Download information about the device. This saves the results in shared-preferences so - * future requests will not need a network connection. + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. * - * @param callback the callback to retrieve the {@link DeviceName.DeviceInfo} + * @param context the application context. + * @return {@link DeviceInfo} for the current device. */ - public void request(Callback callback) { - if (codename == null && model == null) { - codename = Build.DEVICE; - model = Build.MODEL; - } - GetDeviceRunnable runnable = new GetDeviceRunnable(callback); - if (Looper.myLooper() == Looper.getMainLooper()) { - new Thread(runnable).start(); - } else { - runnable.run(); // already running in background thread. - } + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context) { + return getDeviceInfo(context.getApplicationContext(), Build.DEVICE, Build.MODEL); } - private final class GetDeviceRunnable implements Runnable { + public static String getDeviceFullName(Context context) { + DeviceInfo deviceInfo = getDeviceInfo(context.getApplicationContext(), Build.DEVICE, Build.MODEL); + return deviceInfo.manufacturer + " " + deviceInfo.marketName; + } - final Callback callback; - DeviceInfo deviceInfo; - Exception error; + /** + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. + * + * @param context the application context. + * @param codename the codename of the device + * @return {@link DeviceInfo} for the current device. + */ + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context, String codename) { + return getDeviceInfo(context, codename, null); + } - GetDeviceRunnable(Callback callback) { - this.callback = callback; - } + /** + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. + * + * @param context the application context. + * @param codename the codename of the device + * @param model the model of the device + * @return {@link DeviceInfo} for the current device. + */ + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context, String codename, String model) { + SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + String key = String.format("%s:%s", codename, model); + String savedJson = prefs.getString(key, null); + if (savedJson != null) { + try { + return new DeviceInfo(new JSONObject(savedJson)); + } catch (JSONException e) { + e.printStackTrace(); + } + } - @Override - public void run() { - try { - deviceInfo = getDeviceInfo(context, codename, model); + try (DeviceDatabase database = new DeviceDatabase(context)) { + DeviceInfo info = database.queryToDevice(codename, model); + if (info != null) { + JSONObject json = new JSONObject(); + if (info.manufacturer == null) { + json.put("manufacturer", capitalize(Build.MANUFACTURER)); + } else { + json.put("manufacturer", info.manufacturer); + } + json.put("codename", info.codename); + json.put("model", info.model); + if (info.marketName == null) { + json.put("market_name", capitalize(Build.MODEL)); + } else { + json.put("market_name", info.marketName); + } + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(key, json.toString()); + editor.apply(); + return info; + } } catch (Exception e) { - error = e; + e.printStackTrace(); + } + + if (codename.equals(Build.DEVICE) && Build.MODEL.equals(model)) { + return new DeviceInfo(Build.MANUFACTURER, codename, codename, model); // current device } - handler.post(new Runnable() { - - @Override - public void run() { - callback.onFinished(deviceInfo, error); - } - }); - } - } - } - /** - * Callback which is invoked when the {@link DeviceName.DeviceInfo} is finished loading. - */ - public interface Callback { + return new DeviceInfo(null, null, codename, model); // unknown device + } /** - * Callback to get the device info. This is run on the UI thread. + *Capitalizes getAllProcesses the whitespace separated words in a String. Only the first + * letter of each word is changed.
+ *+ * Whitespace is defined by {@link Character#isWhitespace(char)}. * - * @param info the requested {@link DeviceName.DeviceInfo} - * @param error {@code null} if nothing went wrong. + * @param str the String to capitalize + * @return capitalized The capitalized String */ - void onFinished(DeviceInfo info, Exception error); - } + private static String capitalize(String str) { + if (TextUtils.isEmpty(str)) { + return str; + } + char[] arr = str.toCharArray(); + boolean capitalizeNext = true; + StringBuilder phrase = new StringBuilder(); + for (char c : arr) { + if (capitalizeNext && Character.isLetter(c)) { + phrase.append(Character.toUpperCase(c)); + capitalizeNext = false; + continue; + } else if (Character.isWhitespace(c)) { + capitalizeNext = true; + } + phrase.append(c); + } + return phrase.toString(); + } - /** - * Device information based on - * Google's maintained list. - */ - public static final class DeviceInfo { + public static final class Request { - /** Retail branding */ - @Deprecated - public final String manufacturer; + final Context context; + final Handler handler; + String codename; + String model; - /** Marketing name */ - public final String marketName; + private Request(Context ctx) { + context = ctx; + handler = new Handler(ctx.getMainLooper()); + } - /** the value of the system property "ro.product.device" */ - public final String codename; + /** + * Set the device codename to query. You should also set the model. + * + * @param codename the value of the system property "ro.product.device" + * @return This Request object to allow for chaining of calls to set methods. + * @see Build#DEVICE + */ + public Request setCodename(String codename) { + this.codename = codename; + return this; + } - /** the value of the system property "ro.product.model" */ - public final String model; + /** + * Set the device model to query. You should also set the codename. + * + * @param model the value of the system property "ro.product.model" + * @return This Request object to allow for chaining of calls to set methods. + * @see Build#MODEL + */ + public Request setModel(String model) { + this.model = model; + return this; + } - public DeviceInfo(String marketName, String codename, String model) { - this(null, marketName, codename, model); - } + /** + * Download information about the device. This saves the results in shared-preferences so + * future requests will not need a network connection. + * + * @param callback the callback to retrieve the {@link DeviceName.DeviceInfo} + */ + public void request(Callback callback) { + if (codename == null && model == null) { + codename = Build.DEVICE; + model = Build.MODEL; + } + GetDeviceRunnable runnable = new GetDeviceRunnable(callback); + if (Looper.myLooper() == Looper.getMainLooper()) { + new Thread(runnable).start(); + } else { + runnable.run(); // already running in background thread. + } + } - public DeviceInfo(String manufacturer, String marketName, String codename, String model) { - this.manufacturer = manufacturer; - this.marketName = marketName; - this.codename = codename; - this.model = model; + private final class GetDeviceRunnable implements Runnable { + + final Callback callback; + DeviceInfo deviceInfo; + Exception error; + + GetDeviceRunnable(Callback callback) { + this.callback = callback; + } + + @Override + public void run() { + try { + deviceInfo = getDeviceInfo(context, codename, model); + } catch (Exception e) { + error = e; + } + handler.post(new Runnable() { + + @Override + public void run() { + callback.onFinished(deviceInfo, error); + } + }); + } + } } - private DeviceInfo(JSONObject jsonObject) throws JSONException { - manufacturer = jsonObject.getString("manufacturer"); - marketName = jsonObject.getString("market_name"); - codename = jsonObject.getString("codename"); - model = jsonObject.getString("model"); + /** + * Callback which is invoked when the {@link DeviceName.DeviceInfo} is finished loading. + */ + public interface Callback { + + /** + * Callback to get the device info. This is run on the UI thread. + * + * @param info the requested {@link DeviceName.DeviceInfo} + * @param error {@code null} if nothing went wrong. + */ + void onFinished(DeviceInfo info, Exception error); } /** - * @return the consumer friendly name of the device. + * Device information based on + * Google's maintained list. */ - public String getName() { - if (!TextUtils.isEmpty(marketName)) { - return marketName; - } - return capitalize(model); - } - } - - @SuppressLint("PrivateApi") - private static Context context() { - if (context != null) return context; - - // We didn't use to require holding onto the application context so let's cheat a little. - try { - return (Application) Class.forName("android.app.ActivityThread") - .getMethod("currentApplication") - .invoke(null, (Object[]) null); - } catch (Exception ignored) { - } + public static final class DeviceInfo { + + /** + * Retail branding + */ + @Deprecated + public final String manufacturer; + + /** + * Marketing name + */ + public final String marketName; + + /** + * the value of the system property "ro.product.device" + */ + public final String codename; + + /** + * the value of the system property "ro.product.model" + */ + public final String model; + + public DeviceInfo(String marketName, String codename, String model) { + this(null, marketName, codename, model); + } + + public DeviceInfo(String manufacturer, String marketName, String codename, String model) { + this.manufacturer = manufacturer; + this.marketName = marketName; + this.codename = codename; + this.model = model; + } - // Last attempt at hackery - try { - return (Application) Class.forName("android.app.AppGlobals") - .getMethod("getInitialApplication") - .invoke(null, (Object[]) null); - } catch (Exception ignored) { + private DeviceInfo(JSONObject jsonObject) throws JSONException { + if (jsonObject.has("manufacturer")) { + manufacturer = jsonObject.getString("manufacturer"); + }else { + manufacturer = null; + } + if (jsonObject.has("market_name")) { + marketName = jsonObject.getString("market_name"); + }else { + marketName = null; + } + if (jsonObject.has("codename")) { + codename = jsonObject.getString("codename"); + }else { + codename = null; + } + if (jsonObject.has("model")) { + model = jsonObject.getString("model"); + }else { + model = null; + } + } + + /** + * @return the consumer friendly name of the device. + */ + public String getName() { + if (!TextUtils.isEmpty(marketName)) { + return marketName; + } + return capitalize(model); + } } - throw new RuntimeException("DeviceName must be initialized before usage."); - } + @SuppressLint("PrivateApi") + private static Context context() { + if (context != null) return context; + + // We didn't use to require holding onto the application context so let's cheat a little. + try { + return (Application) Class.forName("android.app.ActivityThread") + .getMethod("currentApplication") + .invoke(null, (Object[]) null); + } catch (Exception ignored) { + } + + // Last attempt at hackery + try { + return (Application) Class.forName("android.app.AppGlobals") + .getMethod("getInitialApplication") + .invoke(null, (Object[]) null); + } catch (Exception ignored) { + } + + throw new RuntimeException("DeviceName must be initialized before usage."); + } }