1
0
Fork 0

code cleanup: a simpler way of detecting the Android version

This commit is contained in:
sspanak 2025-02-25 16:41:27 +02:00 committed by Dimo Karaivanov
parent 3b4ded4362
commit adf9363f62
26 changed files with 197 additions and 180 deletions

View file

@ -5,7 +5,6 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
@ -16,6 +15,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.util.Permissions;
public abstract class AbstractExporter extends AbstractFileProcessor {
@ -83,7 +83,7 @@ public abstract class AbstractExporter extends AbstractFileProcessor {
protected void write(Activity activity) throws Exception {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (DeviceInfo.AT_LEAST_ANDROID_10) {
writeAndroid10(activity);
} else {
writeLegacy(activity);
@ -98,7 +98,7 @@ public abstract class AbstractExporter extends AbstractFileProcessor {
public String getOutputDir() {
// on some older phones, files may not be visible in the DOCUMENTS directory, so we use DOWNLOADS
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? Environment.DIRECTORY_DOCUMENTS : Environment.DIRECTORY_DOWNLOADS;
return DeviceInfo.AT_LEAST_ANDROID_10 ? Environment.DIRECTORY_DOCUMENTS : Environment.DIRECTORY_DOWNLOADS;
}

View file

@ -1,31 +1,31 @@
package io.github.sspanak.tt9.hacks;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.R;
public class DeviceInfo {
private static Resources resources;
public class DeviceInfo extends HardwareInfo {
public static final boolean AT_LEAST_ANDROID_6 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
public static final boolean AT_LEAST_ANDROID_7 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
public static final boolean AT_LEAST_ANDROID_8 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
public static final boolean AT_LEAST_ANDROID_8_1 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1;
public static final boolean AT_LEAST_ANDROID_9 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
public static final boolean AT_LEAST_ANDROID_10 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
public static final boolean AT_LEAST_ANDROID_11 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
public static final boolean AT_LEAST_ANDROID_12 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
public static final boolean AT_LEAST_ANDROID_13 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
public static final boolean AT_LEAST_ANDROID_14 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
public static final boolean AT_LEAST_ANDROID_15 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
private static Resources getResources(Context context) {
if (resources == null) {
resources = context.getResources();
}
return resources;
}
public static boolean isLandscapeOrientation(Context context) {
return getResources(context).getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}
public static int getNavigationBarHeight(Context context, boolean isLandscape) {
Resources resources = getResources(context);
@ -40,73 +40,4 @@ public class DeviceInfo {
return navBarHeight;
}
}
public static int getScreenWidth(Context context) {
return getResources(context).getDisplayMetrics().widthPixels;
}
public static int getScreenHeight(Context context) {
return getResources(context).getDisplayMetrics().heightPixels;
}
public static float getScreenHeightDp(Context context) {
return getScreenHeight(context) / getResources(context).getDisplayMetrics().density;
}
public static float getScreenWidthDp(Context context) {
return getScreenWidth(context) / getResources(context).getDisplayMetrics().density;
}
public static boolean noBackspaceKey() {
return !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL) && !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_CLEAR);
}
public static boolean noKeyboard(Context context) {
// all Xiaomi phones are only touchscreen, but some of them report they have a keyboard
// See: https://github.com/sspanak/tt9/issues/549
if (DeviceInfo.isXiaomi()) {
return true;
}
Configuration configuration = getResources(context).getConfiguration();
return
(configuration.keyboard == Configuration.KEYBOARD_NOKEYS || configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES)
&& !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_STAR)
&& !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POUND);
}
public static boolean noTouchScreen(Context context) {
return !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
}
public static boolean isCatS22Flip() {
return Build.MANUFACTURER.equals("Cat") && Build.MODEL.contains("S22");
}
public static boolean isLgX100S() {
return Build.MANUFACTURER.equals("LGE") && Build.MODEL.contains("X100S");
}
public static boolean isQinF21() {
return Build.MANUFACTURER.equals("DuoQin") && Build.MODEL.contains("F21");
}
public static boolean isSonim() {
return Build.MANUFACTURER.equals("Sonimtech");
}
public static boolean isSonimGen2(Context context) {
return isSonim() && Build.VERSION.SDK_INT == Build.VERSION_CODES.R && noTouchScreen(context);
}
public static boolean isXiaomi() {
return Build.MANUFACTURER.equals("Xiaomi");
}
@NonNull
@Override
public String toString() {
return "\"" + Build.MANUFACTURER + "\" " + "\"" + Build.MODEL + "\"";
}
}

View file

@ -0,0 +1,107 @@
package io.github.sspanak.tt9.hacks;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
public class HardwareInfo {
private static Resources resources;
protected static Resources getResources(Context context) {
if (resources == null) {
resources = context.getResources();
}
return resources;
}
public static int getScreenWidth(Context context) {
return getResources(context).getDisplayMetrics().widthPixels;
}
public static int getScreenHeight(Context context) {
return getResources(context).getDisplayMetrics().heightPixels;
}
public static float getScreenHeightDp(Context context) {
return getScreenHeight(context) / getResources(context).getDisplayMetrics().density;
}
public static float getScreenWidthDp(Context context) {
return getScreenWidth(context) / getResources(context).getDisplayMetrics().density;
}
public static boolean noBackspaceKey() {
return !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL) && !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_CLEAR);
}
public static boolean noKeyboard(Context context) {
// all Xiaomi phones are only touchscreen, but some of them report they have a keyboard
// See: https://github.com/sspanak/tt9/issues/549
if (isXiaomi()) {
return true;
}
Configuration configuration = getResources(context).getConfiguration();
return
(configuration.keyboard == Configuration.KEYBOARD_NOKEYS || configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES)
&& !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_STAR)
&& !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POUND);
}
public static boolean noTouchScreen(Context context) {
return !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
}
public static boolean isCatS22Flip() {
return Build.MANUFACTURER.equals("Cat") && Build.MODEL.contains("S22");
}
public static boolean isLgX100S() {
return Build.MANUFACTURER.equals("LGE") && Build.MODEL.contains("X100S");
}
public static boolean isQinF21() {
return Build.MANUFACTURER.equals("DuoQin") && Build.MODEL.contains("F21");
}
public static boolean isSonim() {
return Build.MANUFACTURER.equals("Sonimtech");
}
public static boolean isSonimGen2(Context context) {
return isSonim() && DeviceInfo.AT_LEAST_ANDROID_11 && noTouchScreen(context);
}
public static boolean isXiaomi() {
return Build.MANUFACTURER.equals("Xiaomi");
}
@NonNull
@Override
public String toString() {
return "\"" + Build.MANUFACTURER + "\" " + "\"" + Build.MODEL + "\"";
}
}

View file

@ -1,10 +1,9 @@
package io.github.sspanak.tt9.ime;
import android.os.Build;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DataStore;
import io.github.sspanak.tt9.db.words.DictionaryLoader;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.ime.modes.InputModeKind;
import io.github.sspanak.tt9.languages.LanguageCollection;
@ -126,7 +125,7 @@ abstract public class CommandHandler extends TextEditingHandler {
suggestionOps.cancelDelayedAccept();
stopVoiceInput();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (DeviceInfo.AT_LEAST_ANDROID_9) {
switchToPreviousInputMethod();
return;
}

View file

@ -1,6 +1,5 @@
package io.github.sspanak.tt9.ime;
import android.os.Build;
import android.view.inputmethod.InputMethodManager;
import io.github.sspanak.tt9.R;
@ -80,7 +79,7 @@ abstract class UiHandler extends AbstractHandler {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (DeviceInfo.AT_LEAST_ANDROID_9) {
requestShowSelf(DeviceInfo.isSonimGen2(getApplicationContext()) ? 0 : InputMethodManager.SHOW_IMPLICIT);
} else {
showWindow(true);

View file

@ -1,6 +1,5 @@
package io.github.sspanak.tt9.ime.helpers;
import android.os.Build;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@ -8,6 +7,7 @@ import androidx.annotation.Nullable;
import java.util.ArrayList;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
@ -85,7 +85,7 @@ public class InputField {
*/
@Nullable
public Language getLanguage(ArrayList<Integer> allowedLanguageIds) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || field == null || field.hintLocales == null) {
if (!DeviceInfo.AT_LEAST_ANDROID_7 || field == null || field.hintLocales == null) {
return null;
}

View file

@ -1,12 +1,12 @@
package io.github.sspanak.tt9.ime.voice;
import android.content.Context;
import android.os.Build;
import android.speech.SpeechRecognizer;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.hacks.DeviceInfo;
public class VoiceInputError {
public final static int ERROR_NOT_AVAILABLE = 101;
@ -35,9 +35,9 @@ public class VoiceInputError {
code == SpeechRecognizer.ERROR_NO_MATCH
|| code == SpeechRecognizer.ERROR_SPEECH_TIMEOUT
|| code == SpeechRecognizer.ERROR_AUDIO
|| (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && code == SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS)
|| (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && code == SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT)
|| (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && code == SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
|| (DeviceInfo.AT_LEAST_ANDROID_12 && code == SpeechRecognizer.ERROR_SERVER_DISCONNECTED)
|| (DeviceInfo.AT_LEAST_ANDROID_13 && code == SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT)
|| (DeviceInfo.AT_LEAST_ANDROID_14 && code == SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
}
@ -72,51 +72,36 @@ public class VoiceInputError {
private static String codeToDebugString(int code) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && code == SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS) {
if (DeviceInfo.AT_LEAST_ANDROID_14 && code == SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS) {
return "Cannot listen to download events.";
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && code == SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT) {
if (DeviceInfo.AT_LEAST_ANDROID_13 && code == SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT) {
return "Cannot check voice input support.";
}
String message = codeToDebugString31(code);
message = message != null ? message : codeToDebugStringCommon(code);
String message = codeToDebugStringCommon(code);
message = message != null ? message : "Unknown voice input error code: " + code;
return message;
}
private static String codeToDebugString31(int code) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
switch (code) {
case SpeechRecognizer.ERROR_TOO_MANY_REQUESTS:
return "Server overloaded. Try again later.";
case SpeechRecognizer.ERROR_SERVER_DISCONNECTED:
return "Lost connection to the server.";
case SpeechRecognizer.ERROR_LANGUAGE_NOT_SUPPORTED:
return "Language not supported.";
case SpeechRecognizer.ERROR_LANGUAGE_UNAVAILABLE:
return "Language missing. Try again later.";
}
}
return null;
}
private static String codeToDebugStringCommon(int code) {
return switch (code) {
case SpeechRecognizer.ERROR_AUDIO -> "Audio capture error.";
case SpeechRecognizer.ERROR_CLIENT -> "Speech recognition client error.";
case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS -> "No microphone permissions.";
case SpeechRecognizer.ERROR_LANGUAGE_NOT_SUPPORTED -> "Language not supported.";
case SpeechRecognizer.ERROR_LANGUAGE_UNAVAILABLE -> "Language missing. Try again later.";
case SpeechRecognizer.ERROR_NETWORK -> "No network connection.";
case SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> "Network timeout.";
case SpeechRecognizer.ERROR_NO_MATCH -> "No match.";
case SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> "Voice input service is busy.";
case SpeechRecognizer.ERROR_SERVER -> "Speech recognition server error.";
case SpeechRecognizer.ERROR_SERVER_DISCONNECTED -> "Lost connection to the server.";
case SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> "No speech detected.";
case SpeechRecognizer.ERROR_TOO_MANY_REQUESTS -> "Server overloaded. Try again later.";
case ERROR_NOT_AVAILABLE -> "Voice input is not available.";
case ERROR_INVALID_LANGUAGE -> "Invalid language for voice input.";
case ERROR_CANNOT_BIND_TO_VOICE_SERVICE -> "Cannot bind to the current voice input service.";

View file

@ -2,7 +2,6 @@ package io.github.sspanak.tt9.ime.voice;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
@ -11,6 +10,7 @@ import androidx.annotation.NonNull;
import java.util.ArrayList;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.util.Logger;
@ -35,7 +35,7 @@ public class VoiceInputOps {
ConsumerCompat<String> onStop,
ConsumerCompat<VoiceInputError> onError
) {
isOnDeviceRecognitionAvailable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && SpeechRecognizer.isOnDeviceRecognitionAvailable(ims);
isOnDeviceRecognitionAvailable = DeviceInfo.AT_LEAST_ANDROID_12 && SpeechRecognizer.isOnDeviceRecognitionAvailable(ims);
isRecognitionAvailable = SpeechRecognizer.isRecognitionAvailable(ims);
listener = new VoiceListener(ims, onStart, this::onStop, this::onError);
@ -47,7 +47,7 @@ public class VoiceInputOps {
private void createRecognizer() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && isOnDeviceRecognitionAvailable) {
if (DeviceInfo.AT_LEAST_ANDROID_12 && isOnDeviceRecognitionAvailable) {
speechRecognizer = SpeechRecognizer.createOnDeviceSpeechRecognizer(ims);
} else if (isRecognitionAvailable) {
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(ims);

View file

@ -2,7 +2,6 @@ package io.github.sspanak.tt9.preferences.items;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
@ -11,13 +10,11 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.preference.PreferenceViewHolder;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.languages.LanguageKind;
abstract public class ItemSearch extends ItemTextInput {
private final boolean isModernDevice = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
public ItemSearch(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@ -33,7 +30,7 @@ abstract public class ItemSearch extends ItemTextInput {
@Override protected int getLargeLayout() {
return isModernDevice ? R.layout.pref_input_text : R.layout.pref_input_text_large;
return DeviceInfo.AT_LEAST_ANDROID_12 ? R.layout.pref_input_text : R.layout.pref_input_text_large;
}

View file

@ -1,10 +1,9 @@
package io.github.sspanak.tt9.preferences.items;
import android.os.Build;
import androidx.preference.Preference;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.ui.UI;
import io.github.sspanak.tt9.util.Clipboard;
@ -29,7 +28,7 @@ public class ItemText extends ItemClickable {
p.getSummary()
);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
if (!DeviceInfo.AT_LEAST_ANDROID_13) {
UI.toast(activity, "\"" + Clipboard.getPreview(activity) + "\" copied.");
}

View file

@ -1,12 +1,12 @@
package io.github.sspanak.tt9.preferences.screens;
import android.os.Build;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceFragmentCompat;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.custom.ScreenPreferencesList;
import io.github.sspanak.tt9.preferences.settings.SettingsUI;
@ -97,7 +97,7 @@ abstract public class BaseScreenFragment extends PreferenceFragmentCompat {
public void resetFontSize(boolean reloadList) {
initPreferencesList();
preferencesList.getAll(reloadList, true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (DeviceInfo.AT_LEAST_ANDROID_12) {
preferencesList.setFontSize(SettingsUI.FONT_SIZE_DEFAULT);
} else {
preferencesList.setFontSize(activity.getSettings().getSettingsFontSize());

View file

@ -1,13 +1,12 @@
package io.github.sspanak.tt9.preferences.screens.appearance;
import android.os.Build;
import androidx.preference.DropDownPreference;
import androidx.preference.Preference;
import java.util.LinkedHashMap;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.items.ItemDropDown;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
@ -21,7 +20,7 @@ public class ItemSelectSettingsFontSize extends ItemDropDown {
}
public ItemDropDown populate() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (DeviceInfo.AT_LEAST_ANDROID_12) {
if (item != null) item.setVisible(false);
return this;
}

View file

@ -1,7 +1,6 @@
package io.github.sspanak.tt9.preferences.settings;
import android.content.Context;
import android.os.Build;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.screens.debug.ItemInputHandlingMode;
@ -38,7 +37,7 @@ class SettingsHacks extends BaseSettings {
/************* hack settings *************/
public int getSuggestionScrollingDelay() {
boolean defaultOn = DeviceInfo.noTouchScreen(context) && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;
boolean defaultOn = DeviceInfo.noTouchScreen(context) && !DeviceInfo.AT_LEAST_ANDROID_10;
return prefs.getBoolean("pref_alternative_suggestion_scrolling", defaultOn) ? 200 : 0;
}

View file

@ -1,15 +1,16 @@
package io.github.sspanak.tt9.ui;
import android.os.Build;
import android.view.View;
import android.view.WindowInsets;
import androidx.appcompat.app.AppCompatActivity;
import io.github.sspanak.tt9.hacks.DeviceInfo;
public class EdgeToEdgeActivity extends AppCompatActivity {
public void preventEdgeToEdge(View view) {
if (view == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
if (view == null || !DeviceInfo.AT_LEAST_ANDROID_15) {
return;
}
@ -33,13 +34,13 @@ public class EdgeToEdgeActivity extends AppCompatActivity {
}
private void applyThemeToSystemUi() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (DeviceInfo.AT_LEAST_ANDROID_10) {
getWindow().setStatusBarContrastEnforced(true);
}
}
private WindowInsets getInsets(View view) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if (!DeviceInfo.AT_LEAST_ANDROID_6) {
return null;
}

View file

@ -5,7 +5,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.Looper;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
@ -16,6 +15,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.HashMap;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
public class UI {
@ -56,8 +56,8 @@ public class UI {
public static void confirm(Context context, String title, String message, String OKLabel, Runnable onOk, Runnable onCancel) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
new AlertDialog.Builder(context)
if (DeviceInfo.AT_LEAST_ANDROID_12) {
new MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); })
@ -65,7 +65,7 @@ public class UI {
.setCancelable(false)
.show();
} else {
new MaterialAlertDialogBuilder(context)
new AlertDialog.Builder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); })

View file

@ -1,11 +1,11 @@
package io.github.sspanak.tt9.ui;
import android.os.Build;
import android.view.HapticFeedbackConstants;
import android.view.View;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.ui.main.keys.BaseClickableKey;
import io.github.sspanak.tt9.ui.main.keys.SoftKeyNumber;
@ -28,7 +28,7 @@ public class Vibration {
}
public static int getHoldVibration() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (DeviceInfo.AT_LEAST_ANDROID_11) {
return HapticFeedbackConstants.CONFIRM;
} else {
return HapticFeedbackConstants.VIRTUAL_KEY;
@ -36,7 +36,7 @@ public class Vibration {
}
public static int getReleaseVibration() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
if (DeviceInfo.AT_LEAST_ANDROID_8_1) {
return HapticFeedbackConstants.KEYBOARD_RELEASE;
} else {
return HapticFeedbackConstants.VIRTUAL_KEY;

View file

@ -2,13 +2,13 @@ package io.github.sspanak.tt9.ui;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.util.Clipboard;
import io.github.sspanak.tt9.util.Logger;
@ -26,9 +26,9 @@ public class WebViewSafeClient extends WebViewClient {
return super.shouldOverrideUrlLoading(view, url);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || !shareLink(url)) {
if (!DeviceInfo.AT_LEAST_ANDROID_10 || !shareLink(url)) {
Clipboard.copy(activity, url);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
if (!DeviceInfo.AT_LEAST_ANDROID_13) {
UI.toastShortSingle(activity, R.string.help_url_copied);
}
}

View file

@ -3,13 +3,13 @@ package io.github.sspanak.tt9.ui.dialogs;
import android.content.Context;
import android.content.Intent;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DataStore;
import io.github.sspanak.tt9.db.entities.AddWordResult;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
@ -33,7 +33,7 @@ public class AddWordDialog extends PopupDialog {
.setSettings(new SettingsStore(context))
// The main theme does not work on Android <= 11 and the _AddWord theme does not work on 12+.
// Not sure why since they inherit from the same parent, but it is what it is.
.setTheme(Build.VERSION.SDK_INT < Build.VERSION_CODES.S ? R.style.TTheme_AddWord : R.style.TTheme)
.setTheme(DeviceInfo.AT_LEAST_ANDROID_12 ? R.style.TTheme : R.style.TTheme_AddWord)
.build(),
activityFinisher
);

View file

@ -64,7 +64,7 @@ abstract class BaseMainLayout {
protected WindowInsets onApplyInsets(@NonNull View v, @NonNull WindowInsets windowInsets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
if (DeviceInfo.AT_LEAST_ANDROID_15) {
return preventEdgeToEdge(v, windowInsets);
} else {
return windowInsets;
@ -100,7 +100,7 @@ abstract class BaseMainLayout {
* is re-created and it is not yet possible to get the new window insets.
*/
public void preventEdgeToEdge() {
if (tt9 == null || view == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
if (tt9 == null || view == null || !DeviceInfo.AT_LEAST_ANDROID_15) {
return;
}
@ -113,7 +113,7 @@ abstract class BaseMainLayout {
void requestPreventEdgeToEdge() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && view != null) {
if (view != null && DeviceInfo.AT_LEAST_ANDROID_15) {
view.requestApplyInsets();
}
}
@ -248,7 +248,7 @@ abstract class BaseMainLayout {
int width = tt9.getSettings().getWidthPercent();
return
Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
DeviceInfo.AT_LEAST_ANDROID_15
&& ((isLandscape && width >= 75) || (!isLandscape && width >= 65));
}

View file

@ -1,11 +1,11 @@
package io.github.sspanak.tt9.ui.main;
import android.os.Build;
import android.view.Gravity;
import android.view.View;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.ime.TraditionalT9;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.ui.Vibration;
@ -29,7 +29,7 @@ public class ResizableMainView extends MainView implements View.OnAttachStateCha
private void calculateSnapHeights() {
boolean forceRecalculate = Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
boolean forceRecalculate = DeviceInfo.AT_LEAST_ANDROID_15;
heightNumpad = new MainLayoutNumpad(tt9).getHeight(forceRecalculate);
heightSmall = new MainLayoutSmall(tt9).getHeight(forceRecalculate);

View file

@ -120,7 +120,7 @@ public class SoftKey extends BaseClickableKey {
if (
getNoEmojiTitle() > 0
&& (
Characters.noEmojiSupported()
Characters.NO_EMOJI_SUPPORT
|| (new Text(getText().toString()).startsWithGraphic() && !new Paint().hasGlyph(getText().toString()))
)
) {

View file

@ -5,12 +5,12 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.languages.Language;
public abstract class DictionaryNotification {
@ -43,7 +43,7 @@ public abstract class DictionaryNotification {
protected abstract PendingIntent createNavigationIntent(@NonNull Context context, @Nullable Language language);
private NotificationCompat.Builder getNotificationBuilderCompat(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (DeviceInfo.AT_LEAST_ANDROID_8) {
manager.createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"Dictionary Status",

View file

@ -4,12 +4,13 @@ package io.github.sspanak.tt9.util;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.NonNull;
import java.util.HashMap;
import io.github.sspanak.tt9.hacks.DeviceInfo;
public class Permissions {
private static final HashMap<String, Boolean> firstTimeAsking = new HashMap<>();
@NonNull private final Activity activity;
@ -20,7 +21,7 @@ public class Permissions {
public boolean noPostNotifications() {
return
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
DeviceInfo.AT_LEAST_ANDROID_13
&& isRefused(Manifest.permission.POST_NOTIFICATIONS)
&& (
Boolean.TRUE.equals(firstTimeAsking.getOrDefault(Manifest.permission.POST_NOTIFICATIONS, true))
@ -29,7 +30,7 @@ public class Permissions {
}
public void requestPostNotifications() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (DeviceInfo.AT_LEAST_ANDROID_13) {
firstTimeAsking.put(Manifest.permission.POST_NOTIFICATIONS, false);
requestPermission(Manifest.permission.POST_NOTIFICATIONS);
}
@ -42,25 +43,25 @@ public class Permissions {
public boolean noWriteStorage() {
return
Build.VERSION.SDK_INT < Build.VERSION_CODES.R
!DeviceInfo.AT_LEAST_ANDROID_11
&& isRefused(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
public void requestWriteStorage() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (!DeviceInfo.AT_LEAST_ANDROID_11) {
requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
}
private void requestPermission(String permission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (DeviceInfo.AT_LEAST_ANDROID_6) {
activity.requestPermissions(new String[] { permission }, 0);
}
}
private boolean isRefused(String permission) {
return
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
DeviceInfo.AT_LEAST_ANDROID_6
&& activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED;
}
}

View file

@ -1,7 +1,6 @@
package io.github.sspanak.tt9.util;
import android.content.Context;
import android.os.Build;
import android.os.LocaleList;
import android.provider.Settings;
import android.view.Window;
@ -14,6 +13,8 @@ import androidx.annotation.Nullable;
import java.util.Locale;
import io.github.sspanak.tt9.hacks.DeviceInfo;
public class SystemSettings {
private static InputMethodManager inputManager;
@ -46,7 +47,7 @@ public class SystemSettings {
@NonNull
public static String getLocale() {
Locale locale = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ? LocaleList.getDefault().get(0) : Locale.getDefault();
Locale locale = DeviceInfo.AT_LEAST_ANDROID_7 ? LocaleList.getDefault().get(0) : Locale.getDefault();
String country = locale.getCountry();
String language = locale.getLanguage();
@ -77,7 +78,7 @@ public class SystemSettings {
* @see : <a href="https://stackoverflow.com/a/77240330">the only working solution</a>.
*/
public static void setNavigationBarDarkTheme(@Nullable Window window, boolean dark) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (!DeviceInfo.AT_LEAST_ANDROID_11) {
return;
}

View file

@ -1,12 +1,15 @@
package io.github.sspanak.tt9.util.chars;
import android.graphics.Paint;
import android.os.Build;
import java.util.ArrayList;
import java.util.Arrays;
import io.github.sspanak.tt9.hacks.DeviceInfo;
class Emoji extends Punctuation {
final public static boolean NO_EMOJI_SUPPORT = !DeviceInfo.AT_LEAST_ANDROID_6;
final private static ArrayList<String> TextEmoticons = new ArrayList<>(Arrays.asList(
":)", ":D", ":P", ";)", "\\m/", ":-O", ":|", ":("
));
@ -34,12 +37,8 @@ class Emoji extends Punctuation {
return !(ch < 256 || Character.isLetterOrDigit(ch) || Character.isAlphabetic(ch));
}
public static boolean noEmojiSupported() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
}
public static ArrayList<String> getEmoji(int level) {
if (noEmojiSupported()) {
if (NO_EMOJI_SUPPORT) {
return new ArrayList<>(TextEmoticons);
}
@ -59,6 +58,6 @@ class Emoji extends Punctuation {
}
public static int getMaxEmojiLevel() {
return noEmojiSupported() ? 1 : Emoji.size();
return NO_EMOJI_SUPPORT ? 1 : Emoji.size();
}
}

View file

@ -1,17 +1,17 @@
package io.github.sspanak.tt9.util.chars;
import android.graphics.Paint;
import android.os.Build;
import java.util.ArrayList;
import java.util.Arrays;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageKind;
class Punctuation {
public static final String GR_QUESTION_MARK = ";";
public static final String NEW_LINE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && new Paint().hasGlyph("") ? "" : "\\n";
public static final String NEW_LINE = DeviceInfo.AT_LEAST_ANDROID_6 && new Paint().hasGlyph("") ? "" : "\\n";
public static final String ZWJ = "\u200D";
public static final String ZWJ_GRAPHIC = "ZWJ";
public static final String ZWNJ = "\u200C";