├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── framework.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── teamjcd │ │ └── bpp │ │ └── ExampleInstrumentedTest.java │ ├── magisk │ ├── META-INF │ │ └── com │ │ │ └── google │ │ │ └── android │ │ │ ├── update-binary │ │ │ └── updater-script │ ├── module.prop │ └── system │ │ ├── etc │ │ └── permissions │ │ │ └── privapp-permissions-com.teamjcd.bpp.xml │ │ └── priv-app │ │ └── BluetoothPlusPlus │ │ └── .keep │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── teamjcd │ │ │ └── bpp │ │ │ ├── activity │ │ │ ├── BppBaseActivity.java │ │ │ ├── BppBaseEditorActivity.java │ │ │ ├── BppDeviceClassEditorActivity.java │ │ │ └── BppMainActivity.java │ │ │ ├── content │ │ │ ├── BppBaseContentProvider.java │ │ │ └── BppDeviceClassContentProvider.java │ │ │ ├── database │ │ │ └── BppDatabaseHelper.java │ │ │ ├── fragment │ │ │ ├── BppBaseEditorFragment.java │ │ │ ├── BppDeviceClassEditorFragment.java │ │ │ └── BppMainFragment.java │ │ │ ├── preference │ │ │ ├── BppBasePreference.java │ │ │ └── BppDeviceClassPreference.java │ │ │ ├── provider │ │ │ ├── BppBaseColumns.java │ │ │ └── BppDeviceClassColumns.java │ │ │ ├── repository │ │ │ ├── BppBaseRepository.java │ │ │ └── BppDeviceClassRepository.java │ │ │ └── util │ │ │ └── BppUtils.java │ ├── res │ │ ├── drawable │ │ │ ├── ic_add_24.xml │ │ │ └── ic_delete_24.xml │ │ ├── layout │ │ │ ├── activity_bpp_device_class_editor.xml │ │ │ ├── activity_bpp_main.xml │ │ │ └── widget_bpp_selectable.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── values-ar │ │ │ └── strings.xml │ │ ├── values-de │ │ │ └── strings.xml │ │ ├── values-es │ │ │ └── strings.xml │ │ ├── values-fr │ │ │ └── strings.xml │ │ ├── values-it │ │ │ └── strings.xml │ │ ├── values-night │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── values-pt-rBR │ │ │ └── strings.xml │ │ ├── values-tr │ │ │ └── strings.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── xml │ │ │ ├── backup_descriptor.xml │ │ │ ├── fragment_bpp_device_class_editor.xml │ │ │ └── fragment_bpp_main.xml │ └── web_hi_res_512.png │ └── test │ └── java │ └── com │ └── github │ └── teamjcd │ └── bpp │ └── ExampleUnitTest.java ├── bluetoothplusplus ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /.idea/ 17 | /app/release/ 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](app/src/main/web_hi_res_512.png) 2 | 3 | # Bluetooth++ 4 | 5 | ## Introduction 6 | Bluetooth++ is a privileged app to manage Bluetooth device classes (CoD). 7 | 8 | It can be installed as a [Magisk](https://github.com/topjohnwu/Magisk) module. 9 | 10 | ## Translation Contributions 11 | Default string resources for the Bluetooth++ app are located here: `app/src/main/res/values/strings.xml`. 12 | 13 | Translate and place it in the respective location (`app/src/main/res/values-[lang]/strings.xml`). 14 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'com.github.breadmoirai.github-release' version '2.2.12' 4 | id 'org.ajoberstar.grgit' 5 | } 6 | 7 | import org.ajoberstar.grgit.Grgit 8 | 9 | def appName = "BluetoothPlusPlus" 10 | 11 | ext { 12 | grgit = Grgit.open(currentDir: projectDir) 13 | projectVersionCode = grgit.log().size() 14 | projectVersionName = "${project.version}" 15 | } 16 | 17 | android { 18 | compileSdkVersion 30 19 | 20 | signingConfigs { 21 | debug { 22 | storeFile file("${project.rootDir}/bluetoothplusplus") 23 | storePassword 'bluetoothplusplus' 24 | keyPassword 'bluetoothplusplus' 25 | keyAlias 'bpp' 26 | } 27 | release { 28 | storeFile file("${project.rootDir}/bluetoothplusplus") 29 | storePassword "bluetoothplusplus" 30 | keyAlias "bpp" 31 | keyPassword "bluetoothplusplus" 32 | 33 | // Optional, specify signing versions used 34 | v1SigningEnabled true 35 | v2SigningEnabled true 36 | } 37 | } 38 | 39 | defaultConfig { 40 | applicationId "com.github.teamjcd.bpp" 41 | minSdkVersion 27 42 | //noinspection ExpiredTargetSdkVersion 43 | targetSdkVersion 27 44 | versionCode projectVersionCode 45 | versionName projectVersionName 46 | 47 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 48 | } 49 | 50 | buildTypes { 51 | release { 52 | minifyEnabled false 53 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 54 | signingConfig signingConfigs.release 55 | } 56 | } 57 | 58 | compileOptions { 59 | sourceCompatibility JavaVersion.VERSION_1_8 60 | targetCompatibility JavaVersion.VERSION_1_8 61 | } 62 | 63 | lintOptions { 64 | checkReleaseBuilds false 65 | disable 'MissingTranslation' 66 | } 67 | 68 | applicationVariants.all { variant -> 69 | variant.outputs.all { 70 | outputFileName = "${appName}.apk" 71 | } 72 | } 73 | } 74 | 75 | githubRelease { 76 | token getProperty('github.token') 77 | owner "TeamJCD" 78 | repo appName 79 | tagName projectVersionName 80 | releaseName "Bluetooth++ ${projectVersionName}" 81 | releaseAssets "${project.buildDir}/distributions/${appName}-${android.defaultConfig.versionName}.zip" 82 | } 83 | 84 | tasks.findByName('githubRelease').dependsOn('packageMagiskModule') 85 | 86 | dependencies { 87 | implementation 'androidx.annotation:annotation-experimental:1.1.0' 88 | implementation 'androidx.appcompat:appcompat:1.3.1' 89 | implementation "androidx.preference:preference:1.1.1" 90 | 91 | implementation 'com.google.android.material:material:1.4.0' 92 | implementation 'androidx.constraintlayout:constraintlayout:2.1.1' 93 | compileOnly fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: []) 94 | testImplementation 'junit:junit:4.13.2' 95 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 96 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 97 | } 98 | 99 | import org.apache.tools.ant.filters.ReplaceTokens 100 | 101 | task copyMagiskFiles(type: Copy) { 102 | from file("src/magisk") 103 | include "**/*" 104 | include "*" 105 | exclude "**/.keep" 106 | into file("${project.buildDir}/outputs/magisk") 107 | filter(ReplaceTokens, tokens: [ 108 | version : "${android.defaultConfig.versionName}".toString(), 109 | versionCode: "${android.defaultConfig.versionCode}".toString(), 110 | appId : "${appName}".toString(), 111 | appName : "${appName}".toString(), 112 | ]) 113 | } 114 | 115 | copyMagiskFiles.dependsOn("assembleRelease") 116 | 117 | task copyApk(type: Copy) { 118 | from file("${project.buildDir}/outputs/apk/release") 119 | include "${appName}.apk" 120 | into file("${project.buildDir}/outputs/magisk/system/priv-app/${appName}") 121 | } 122 | 123 | copyApk.dependsOn("copyMagiskFiles") 124 | 125 | task packageMagiskModule(type: Zip) { 126 | from("${project.buildDir}/outputs/magisk") { 127 | include "*" 128 | include "**/*" 129 | } 130 | archiveBaseName = "${appName}" 131 | archiveVersion = "${android.defaultConfig.versionName}" 132 | } 133 | 134 | packageMagiskModule.dependsOn("copyApk") 135 | 136 | task pushMagiskZip(type: Exec) { 137 | def pushCommand = [ 138 | 'adb', 'push', 139 | "${project.buildDir}/distributions/${appName}-${android.defaultConfig.versionName}.zip", 140 | "/sdcard/Download/" 141 | ] 142 | commandLine pushCommand 143 | } 144 | 145 | pushMagiskZip.dependsOn("packageMagiskModule") 146 | 147 | task printVersion() { 148 | doLast { 149 | println("Version Name: $projectVersionName") 150 | println("Version Code: $projectVersionCode") 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/libs/framework.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamJCD/BluetoothPlusPlus/3ac9bf98fb44f2dfa392a866f3f1342df71ea442/app/libs/framework.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/teamjcd/bpp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.github.teamjcd.bpp", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/magisk/META-INF/com/google/android/update-binary: -------------------------------------------------------------------------------- 1 | #!/sbin/sh 2 | 3 | ################# 4 | # Initialization 5 | ################# 6 | 7 | umask 022 8 | 9 | # echo before loading util_functions 10 | ui_print() { echo "$1"; } 11 | 12 | require_new_magisk() { 13 | ui_print "*******************************" 14 | ui_print " Please install Magisk v20.4+! " 15 | ui_print "*******************************" 16 | exit 1 17 | } 18 | 19 | ######################### 20 | # Load util_functions.sh 21 | ######################### 22 | 23 | OUTFD=$2 24 | ZIPFILE=$3 25 | 26 | mount /data 2>/dev/null 27 | 28 | [ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk 29 | . /data/adb/magisk/util_functions.sh 30 | [ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk 31 | 32 | install_module 33 | exit 0 34 | -------------------------------------------------------------------------------- /app/src/magisk/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK 2 | -------------------------------------------------------------------------------- /app/src/magisk/module.prop: -------------------------------------------------------------------------------- 1 | id=@appId@ 2 | name=@appName@ 3 | version=@version@ 4 | versionCode=@versionCode@ 5 | author=TeamJCD 6 | description=Enables Extended Bluetooth Controls 7 | -------------------------------------------------------------------------------- /app/src/magisk/system/etc/permissions/privapp-permissions-com.teamjcd.bpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/magisk/system/priv-app/BluetoothPlusPlus/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamJCD/BluetoothPlusPlus/3ac9bf98fb44f2dfa392a866f3f1342df71ea442/app/src/magisk/system/priv-app/BluetoothPlusPlus/.keep -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 23 | 24 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/activity/BppBaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.activity; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import androidx.fragment.app.Fragment; 9 | import com.github.teamjcd.bpp.R; 10 | 11 | public abstract class BppBaseActivity extends AppCompatActivity { 12 | private static final String TAG = BppBaseActivity.class.getName(); 13 | 14 | private Fragment mFragment; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedState) { 18 | super.onCreate(savedState); 19 | 20 | Log.d(TAG, "Starting onCreate"); 21 | 22 | setContentView(getLayoutResId()); 23 | setSupportActionBar(findViewById(R.id.toolbar)); 24 | setFragment(getSupportFragmentManager().findFragmentById(getFragmentResId())); 25 | } 26 | 27 | protected void setFragment(Fragment fragment) { 28 | mFragment = fragment; 29 | } 30 | 31 | @Override 32 | public boolean onSupportNavigateUp() { 33 | onBackPressed(); 34 | return true; 35 | } 36 | 37 | @Override 38 | public boolean onCreateOptionsMenu(Menu menu) { 39 | mFragment.onCreateOptionsMenu(menu, getMenuInflater()); 40 | return true; 41 | } 42 | 43 | @Override 44 | public boolean onOptionsItemSelected(MenuItem item) { 45 | return mFragment.onOptionsItemSelected(item); 46 | } 47 | 48 | protected abstract int getLayoutResId(); 49 | 50 | protected abstract int getFragmentResId(); 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/activity/BppBaseEditorActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.activity; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import androidx.appcompat.app.ActionBar; 6 | 7 | public abstract class BppBaseEditorActivity extends BppBaseActivity { 8 | private static final String TAG = BppBaseEditorActivity.class.getName(); 9 | 10 | @Override 11 | protected void onCreate(Bundle savedState) { 12 | super.onCreate(savedState); 13 | 14 | Log.d(TAG, "Starting onCreate"); 15 | 16 | ActionBar actionBar = getSupportActionBar(); 17 | if (actionBar != null) { 18 | actionBar.setDisplayHomeAsUpEnabled(true); 19 | actionBar.setDisplayShowHomeEnabled(true); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/activity/BppDeviceClassEditorActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.activity; 2 | 3 | import com.github.teamjcd.bpp.R; 4 | 5 | public class BppDeviceClassEditorActivity extends BppBaseEditorActivity { 6 | @Override 7 | protected int getLayoutResId() { 8 | return R.layout.activity_bpp_device_class_editor; 9 | } 10 | 11 | @Override 12 | protected int getFragmentResId() { 13 | return R.id.fragment_bpp_device_class_editor; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/activity/BppMainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.activity; 2 | 3 | import com.github.teamjcd.bpp.R; 4 | 5 | public class BppMainActivity extends BppBaseActivity { 6 | @Override 7 | protected int getLayoutResId() { 8 | return R.layout.activity_bpp_main; 9 | } 10 | 11 | @Override 12 | protected int getFragmentResId() { 13 | return R.id.fragment_bpp_main; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/content/BppBaseContentProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.content; 2 | 3 | import android.content.ContentProvider; 4 | import android.content.ContentValues; 5 | import android.content.UriMatcher; 6 | import android.database.Cursor; 7 | import android.database.sqlite.SQLiteDatabase; 8 | import android.net.Uri; 9 | import com.github.teamjcd.bpp.database.BppDatabaseHelper; 10 | 11 | import java.util.Objects; 12 | 13 | import static android.provider.BaseColumns._ID; 14 | import static com.github.teamjcd.bpp.database.BppDatabaseHelper.COLUMN_IS_DEFAULT; 15 | import static com.github.teamjcd.bpp.database.BppDatabaseHelper.COLUMN_VALUE; 16 | 17 | public abstract class BppBaseContentProvider extends ContentProvider { 18 | public static final String AUTHORITY = Objects.requireNonNull(BppBaseContentProvider.class.getPackage()).getName(); 19 | public static final String DEFAULT_URI = "default"; 20 | 21 | protected static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY); 22 | 23 | private static final int ROOT = 0; 24 | private static final int ID = 1; 25 | private static final int DEFAULT = 2; 26 | 27 | private final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 28 | 29 | private SQLiteDatabase database; 30 | 31 | public BppBaseContentProvider() { 32 | URI_MATCHER.addURI(AUTHORITY, getTable(), ROOT); 33 | URI_MATCHER.addURI(AUTHORITY, getTable() + "/#", ID); 34 | URI_MATCHER.addURI(AUTHORITY, getTable() + "/" + DEFAULT_URI, DEFAULT); 35 | } 36 | 37 | @Override 38 | public boolean onCreate() { 39 | BppDatabaseHelper dbHelper = new BppDatabaseHelper(getContext()); 40 | database = dbHelper.getWritableDatabase(); 41 | return database != null; 42 | } 43 | 44 | @Override 45 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { 46 | int match = URI_MATCHER.match(uri); 47 | 48 | String where = null; 49 | String[] whereArgs = null; 50 | if (match < 0) { 51 | return null; 52 | } else if (match == ID) { 53 | String lastPathSegment = uri.getLastPathSegment(); 54 | where = _ID + " = ?"; 55 | whereArgs = new String[]{ lastPathSegment }; 56 | } else if (match == DEFAULT) { 57 | where = COLUMN_IS_DEFAULT + " = 1"; 58 | } 59 | 60 | return database.query(getTable(), 61 | projection, 62 | where, 63 | whereArgs, 64 | null, 65 | null, 66 | null); 67 | } 68 | 69 | @Override 70 | public String getType(Uri uri) { 71 | switch (URI_MATCHER.match(uri)) { 72 | case ROOT: 73 | return "vnd.android.cursor.dir/vnd.com.github.teamjcd.bpp.content.BppBaseContentProvider.dir"; 74 | case ID: 75 | case DEFAULT: 76 | return "vnd.android.cursor.item/vnd.com.github.teamjcd.bpp.content.BppBaseContentProvider.item"; 77 | default: 78 | throw new IllegalArgumentException("Unknown URI: " + uri); 79 | } 80 | } 81 | 82 | @Override 83 | public Uri insert(Uri uri, ContentValues values) { 84 | int match = URI_MATCHER.match(uri); 85 | 86 | if (match != ROOT) { 87 | return null; 88 | } 89 | 90 | return Uri.withAppendedPath( 91 | uri, String.valueOf(database.insert(getTable(), null, values))); 92 | } 93 | 94 | @Override 95 | public int delete(Uri uri, String selection, String[] selectionArgs) { 96 | int match = URI_MATCHER.match(uri); 97 | 98 | if (match != ID) { 99 | return 0; 100 | } 101 | 102 | String lastPathSegment = uri.getLastPathSegment(); 103 | return database.delete(getTable(), _ID + " = ?", new String[]{ lastPathSegment }); 104 | } 105 | 106 | @Override 107 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 108 | int match = URI_MATCHER.match(uri); 109 | 110 | if (match != ID) { 111 | values.remove(COLUMN_VALUE); 112 | } 113 | 114 | String lastPathSegment = uri.getLastPathSegment(); 115 | return database.update(getTable(), values, _ID + " = ?", new String[]{ lastPathSegment }); 116 | } 117 | 118 | protected abstract String getTable(); 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/content/BppDeviceClassContentProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.content; 2 | 3 | import android.net.Uri; 4 | 5 | import static com.github.teamjcd.bpp.database.BppDatabaseHelper.TABLE_DEVICE_CLASS; 6 | 7 | public class BppDeviceClassContentProvider extends BppBaseContentProvider { 8 | public static final Uri URI = Uri.withAppendedPath(BASE_URI, TABLE_DEVICE_CLASS); 9 | 10 | @Override 11 | protected String getTable() { 12 | return TABLE_DEVICE_CLASS; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/database/BppDatabaseHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.database; 2 | 3 | import android.content.Context; 4 | import android.content.ContextWrapper; 5 | import android.database.DatabaseErrorHandler; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.database.sqlite.SQLiteOpenHelper; 8 | import android.util.Log; 9 | 10 | import androidx.core.content.ContextCompat; 11 | 12 | import java.io.File; 13 | 14 | import static android.provider.BaseColumns._ID; 15 | 16 | public class BppDatabaseHelper extends SQLiteOpenHelper { 17 | private static final String TAG = BppDatabaseHelper.class.getName(); 18 | 19 | public static final String DATABASE = "BluetoothPlusPlus"; 20 | public static final int DATABASE_VERSION = 1; 21 | 22 | public static final String TABLE_DEVICE_CLASS = "DeviceClass"; 23 | public static final String TABLE_ADDRESS = "Address"; 24 | 25 | public static final String COLUMN_NAME = "name"; 26 | public static final String COLUMN_VALUE = "value"; 27 | public static final String COLUMN_IS_DEFAULT = "is_default"; 28 | 29 | public static final String[] PROJECTION = new String[]{ 30 | _ID, 31 | COLUMN_NAME, 32 | COLUMN_VALUE, 33 | COLUMN_IS_DEFAULT 34 | }; 35 | 36 | public static final int INDEX_ID = 0; 37 | public static final int INDEX_NAME = 1; 38 | public static final int INDEX_VALUE = 2; 39 | public static final int INDEX_IS_DEFAULT = 3; 40 | 41 | public BppDatabaseHelper(Context context) { 42 | super(new DatabaseContext(context), DATABASE, null, DATABASE_VERSION); 43 | } 44 | 45 | @Override 46 | public void onCreate(SQLiteDatabase db) { 47 | db.execSQL("CREATE TABLE " + TABLE_DEVICE_CLASS + " (" + 48 | "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 49 | COLUMN_NAME + " TEXT NOT NULL," + 50 | COLUMN_VALUE + " INTEGER NOT NULL," + 51 | COLUMN_IS_DEFAULT + " INTEGER DEFAULT 0" + 52 | ");"); 53 | 54 | db.execSQL("CREATE TABLE " + TABLE_ADDRESS + " (" + 55 | "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 56 | COLUMN_NAME + " TEXT NOT NULL," + 57 | COLUMN_VALUE + " INTEGER NOT NULL," + 58 | COLUMN_IS_DEFAULT + " INTEGER DEFAULT 0" + 59 | ");"); 60 | } 61 | 62 | @Override 63 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 64 | throw new UnsupportedOperationException(); 65 | } 66 | 67 | public static class DatabaseContext extends ContextWrapper { 68 | public DatabaseContext(Context base) { 69 | super(base); 70 | } 71 | 72 | @Override 73 | public File getDatabasePath(String name) { 74 | File[] externalStorageFiles = ContextCompat.getExternalFilesDirs(this, null); 75 | File externalStorageFile = (externalStorageFiles.length < 2) ? externalStorageFiles[0] : externalStorageFiles[1]; 76 | File databaseFile = new File(externalStorageFile.getAbsolutePath() + File.separator + name); 77 | 78 | if (databaseFile.getParentFile() != null && !databaseFile.getParentFile().exists()) { 79 | if (!databaseFile.getParentFile().mkdirs()) { 80 | Log.e(TAG, "getDatabasePath(): Unable to create directory " 81 | + databaseFile.getParentFile()); 82 | 83 | return super.getDatabasePath(name); 84 | } 85 | } 86 | 87 | Log.d(TAG, "getDatabasePath(): databaseFile - " + databaseFile); 88 | 89 | return databaseFile; 90 | } 91 | 92 | @Override 93 | public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { 94 | return openOrCreateDatabase(name,mode, factory); 95 | } 96 | 97 | @Override 98 | public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { 99 | return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/fragment/BppBaseEditorFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.fragment; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.text.InputType; 9 | import android.text.TextUtils; 10 | import android.util.Log; 11 | import android.view.*; 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | import androidx.appcompat.app.AlertDialog; 15 | import androidx.fragment.app.DialogFragment; 16 | import androidx.fragment.app.FragmentActivity; 17 | import androidx.preference.EditTextPreference; 18 | import androidx.preference.Preference; 19 | import androidx.preference.PreferenceFragmentCompat; 20 | import com.github.teamjcd.bpp.R; 21 | import com.github.teamjcd.bpp.provider.BppBaseColumns; 22 | import com.github.teamjcd.bpp.repository.BppBaseRepository; 23 | import com.github.teamjcd.bpp.util.BppUtils; 24 | 25 | public abstract class BppBaseEditorFragment, T extends BppBaseColumns> extends PreferenceFragmentCompat 26 | implements Preference.OnPreferenceChangeListener, View.OnKeyListener { 27 | private final static String TAG = BppBaseEditorFragment.class.getName(); 28 | 29 | public final static String URI_EXTRA = BppBaseEditorFragment.class.getName() + ".URI_EXTRA"; 30 | 31 | private final int MENU_DELETE = Menu.FIRST; 32 | private final int MENU_SAVE = MENU_DELETE + 1; 33 | private final int MENU_CANCEL = MENU_SAVE + 1; 34 | 35 | private EditTextPreference mName; 36 | private EditTextPreference mValue; 37 | 38 | private U mRepository; 39 | private T mColumns; 40 | 41 | private boolean mNew; 42 | private boolean mReadOnly; 43 | 44 | public EditTextPreference getNamePreference() { 45 | return mName; 46 | } 47 | 48 | public EditTextPreference getValuePreference() { 49 | return mValue; 50 | } 51 | 52 | @Override 53 | public void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | 56 | final FragmentActivity activity = getActivity(); 57 | if (activity != null) { 58 | final Intent intent = activity.getIntent(); 59 | final String action = intent.getAction(); 60 | 61 | if (TextUtils.isEmpty(action)) { 62 | activity.finish(); 63 | return; 64 | } 65 | 66 | Uri uri = null; 67 | if (action.equals(getEditAction())) { 68 | uri = intent.getParcelableExtra(URI_EXTRA); 69 | if (!uri.isPathPrefixMatch(getUriPrefix())) { 70 | Log.e(TAG, "Invalid edit request. Uri: " + uri); 71 | activity.finish(); 72 | return; 73 | } 74 | } else if (action.equals(getInsertAction())) { 75 | Uri insertUri = intent.getParcelableExtra(URI_EXTRA); 76 | if (!insertUri.isPathPrefixMatch(getUriPrefix())) { 77 | Log.e(TAG, "Invalid insert request. Uri: " + insertUri); 78 | activity.finish(); 79 | return; 80 | } 81 | mNew = true; 82 | } else { 83 | activity.finish(); 84 | return; 85 | } 86 | 87 | mRepository = getRepository(getContext()); 88 | 89 | if (uri != null) { 90 | mColumns = mRepository.get(uri); 91 | } else { 92 | mColumns = getColumns(); 93 | } 94 | 95 | mReadOnly = mColumns.isDefault(); 96 | 97 | if (mReadOnly) { 98 | mValue.setEnabled(false); 99 | } 100 | 101 | for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) { 102 | getPreferenceScreen().getPreference(i).setOnPreferenceChangeListener(this); 103 | } 104 | } 105 | } 106 | 107 | @Override 108 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 109 | addPreferencesFromResource(getPreferencesResId()); 110 | 111 | mName = findPreference("name"); 112 | mValue = findPreference("value"); 113 | 114 | mName.setOnBindEditTextListener(editText -> { 115 | editText.setInputType(InputType.TYPE_TEXT_FLAG_CAP_WORDS); 116 | editText.setSelection(editText.getText().length()); 117 | }); 118 | } 119 | 120 | @Override 121 | public void onViewStateRestored(@Nullable Bundle savedInstanceState) { 122 | super.onViewStateRestored(savedInstanceState); 123 | fillUI(savedInstanceState == null); 124 | } 125 | 126 | @Override 127 | public boolean onPreferenceChange(Preference preference, Object newValue) { 128 | preference.setSummary(newValue != null ? String.valueOf(newValue) : null); 129 | return true; 130 | } 131 | 132 | @Override 133 | public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { 134 | super.onCreateOptionsMenu(menu, inflater); 135 | 136 | menu.add(0, MENU_SAVE, 0, R.string.menu_save) 137 | .setIcon(android.R.drawable.ic_menu_save); 138 | 139 | menu.add(0, MENU_CANCEL, 0, R.string.menu_cancel) 140 | .setIcon(android.R.drawable.ic_menu_close_clear_cancel); 141 | 142 | if (!mNew && !mReadOnly) { 143 | menu.add(0, MENU_DELETE, 0, R.string.menu_delete) 144 | .setIcon(R.drawable.ic_delete_24); 145 | } 146 | } 147 | 148 | @Override 149 | public boolean onOptionsItemSelected(MenuItem item) { 150 | FragmentActivity activity = getActivity(); 151 | 152 | switch (item.getItemId()) { 153 | case MENU_DELETE: 154 | mRepository.delete(mColumns.getId()); 155 | if (activity != null) { 156 | activity.finish(); 157 | } 158 | return true; 159 | case MENU_SAVE: 160 | if (validateAndSave()) { 161 | if (activity != null) { 162 | activity.finish(); 163 | } 164 | } 165 | return true; 166 | case MENU_CANCEL: 167 | if (activity != null) { 168 | activity.finish(); 169 | } 170 | return true; 171 | default: 172 | return super.onOptionsItemSelected(item); 173 | } 174 | } 175 | 176 | @Override 177 | public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { 178 | super.onViewCreated(view, savedInstanceState); 179 | view.setOnKeyListener(this); 180 | view.setFocusableInTouchMode(true); 181 | view.requestFocus(); 182 | } 183 | 184 | @Override 185 | public boolean onKey(View v, int keyCode, KeyEvent event) { 186 | if (event.getAction() != KeyEvent.ACTION_DOWN) { 187 | return false; 188 | } 189 | 190 | if (keyCode == KeyEvent.KEYCODE_BACK) { 191 | if (validateAndSave()) { 192 | FragmentActivity activity = getActivity(); 193 | if (activity != null) { 194 | activity.finish(); 195 | } 196 | } 197 | 198 | return true; 199 | } 200 | 201 | return false; 202 | } 203 | 204 | private void fillUI(boolean firstTime) { 205 | if (firstTime) { 206 | mName.setText(mColumns.getName()); 207 | if (!mNew) { 208 | mValue.setText(formatValue(mColumns.getValue())); 209 | } 210 | } 211 | 212 | mName.setSummary(mName.getText()); 213 | mValue.setSummary(mValue.getText()); 214 | } 215 | 216 | private boolean validateAndSave() { 217 | final String errorMsg = validate(); 218 | if (errorMsg != null) { 219 | showError(errorMsg); 220 | return false; 221 | } 222 | 223 | mColumns.setName(mName.getText()); 224 | 225 | if (!mReadOnly) { 226 | mColumns.setValue(BppUtils.parseHex(mValue.getText())); 227 | } 228 | 229 | if (mNew) { 230 | mRepository.save(mColumns); 231 | } else { 232 | mRepository.update(mColumns); 233 | } 234 | 235 | return true; 236 | } 237 | 238 | private void showError(String msg) { 239 | BppBaseEditorFragment.ErrorDialog.showError(this, msg); 240 | } 241 | 242 | public static class ErrorDialog extends DialogFragment { 243 | private String msg; 244 | 245 | public static void showError(PreferenceFragmentCompat editor, String msg) { 246 | BppBaseEditorFragment.ErrorDialog dialog = new BppBaseEditorFragment.ErrorDialog(); 247 | dialog.setMessage(msg); 248 | dialog.show(editor.getChildFragmentManager(), "error"); 249 | } 250 | 251 | @Override 252 | @NonNull 253 | public Dialog onCreateDialog(Bundle savedInstanceState) { 254 | //noinspection ConstantConditions 255 | return new AlertDialog.Builder(getContext()) 256 | .setTitle(R.string.error_title) 257 | .setPositiveButton(android.R.string.ok, null) 258 | .setMessage(msg) 259 | .create(); 260 | } 261 | 262 | private void setMessage(String msg) { 263 | this.msg = msg; 264 | } 265 | } 266 | 267 | protected abstract String getEditAction(); 268 | 269 | protected abstract String getInsertAction(); 270 | 271 | protected abstract Uri getUriPrefix(); 272 | 273 | protected abstract U getRepository(Context context); 274 | 275 | protected abstract T getColumns(); 276 | 277 | protected abstract int getPreferencesResId(); 278 | 279 | protected abstract String validate(); 280 | 281 | protected abstract String formatValue(int value); 282 | } 283 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/fragment/BppDeviceClassEditorFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.fragment; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.text.TextUtils; 6 | import com.github.teamjcd.bpp.R; 7 | import com.github.teamjcd.bpp.content.BppDeviceClassContentProvider; 8 | import com.github.teamjcd.bpp.provider.BppDeviceClassColumns; 9 | import com.github.teamjcd.bpp.repository.BppDeviceClassRepository; 10 | import com.github.teamjcd.bpp.util.BppUtils; 11 | 12 | public class BppDeviceClassEditorFragment extends BppBaseEditorFragment { 13 | @Override 14 | protected String getEditAction() { 15 | return BppMainFragment.ACTION_DEVICE_CLASS_EDIT; 16 | } 17 | 18 | @Override 19 | protected String getInsertAction() { 20 | return BppMainFragment.ACTION_DEVICE_CLASS_INSERT; 21 | } 22 | 23 | @Override 24 | protected Uri getUriPrefix() { 25 | return BppDeviceClassContentProvider.URI; 26 | } 27 | 28 | @Override 29 | protected BppDeviceClassRepository getRepository(Context context) { 30 | return new BppDeviceClassRepository(context); 31 | } 32 | 33 | @Override 34 | protected BppDeviceClassColumns getColumns() { 35 | return new BppDeviceClassColumns(); 36 | } 37 | 38 | @Override 39 | protected int getPreferencesResId() { 40 | return R.xml.fragment_bpp_device_class_editor; 41 | } 42 | 43 | @Override 44 | protected String validate() { 45 | String errMsg = null; 46 | 47 | final String name = getNamePreference().getText(); 48 | final String cod = getValuePreference().getText(); 49 | 50 | if (TextUtils.isEmpty(name)) { 51 | errMsg = getResources().getString(R.string.error_name_empty); 52 | } else if (TextUtils.isEmpty(cod)) { 53 | errMsg = getResources().getString(R.string.error_device_class_empty); 54 | } 55 | 56 | if (errMsg == null) { 57 | try { 58 | BppUtils.parseHex(cod); 59 | } catch (Exception e) { 60 | errMsg = getResources().getString(R.string.error_device_class_invalid); 61 | } 62 | } 63 | 64 | return errMsg; 65 | } 66 | 67 | @Override 68 | protected String formatValue(int value) { 69 | return BppUtils.formatDeviceClass(value); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/fragment/BppMainFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.fragment; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothClass; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.os.Bundle; 10 | import android.util.Log; 11 | import android.view.Menu; 12 | import android.view.MenuInflater; 13 | import android.view.MenuItem; 14 | import androidx.activity.result.contract.ActivityResultContracts; 15 | import androidx.annotation.NonNull; 16 | import androidx.fragment.app.FragmentActivity; 17 | import androidx.preference.Preference; 18 | import androidx.preference.PreferenceFragmentCompat; 19 | import androidx.preference.PreferenceGroup; 20 | import com.github.teamjcd.bpp.R; 21 | import com.github.teamjcd.bpp.activity.BppDeviceClassEditorActivity; 22 | import com.github.teamjcd.bpp.content.BppDeviceClassContentProvider; 23 | import com.github.teamjcd.bpp.preference.BppDeviceClassPreference; 24 | import com.github.teamjcd.bpp.provider.BppDeviceClassColumns; 25 | import com.github.teamjcd.bpp.repository.BppDeviceClassRepository; 26 | import com.github.teamjcd.bpp.util.BppUtils; 27 | 28 | import java.util.List; 29 | 30 | import static android.app.Activity.RESULT_OK; 31 | import static com.github.teamjcd.bpp.fragment.BppBaseEditorFragment.URI_EXTRA; 32 | 33 | public class BppMainFragment extends PreferenceFragmentCompat 34 | implements Preference.OnPreferenceChangeListener { 35 | public static final String ACTION_DEVICE_CLASS_EDIT = BppMainFragment.class.getName() + ".ACTION_DEVICE_CLASS_EDIT"; 36 | public static final String ACTION_DEVICE_CLASS_INSERT = BppMainFragment.class.getName() + ".ACTION_DEVICE_CLASS_INSERT"; 37 | 38 | private static final String TAG = BppMainFragment.class.getName(); 39 | 40 | private static final int FALLBACK_DEFAULT_BLUETOOTH_DEVICE_CLASS = BppUtils.parseHex("5a020c"); 41 | 42 | private static final int MENU_DEVICE_CLASS_NEW = Menu.FIRST; 43 | 44 | private IntentFilter mIntentFilter; 45 | private BluetoothAdapter mAdapter; 46 | private BppDeviceClassRepository mDeviceClassRepository; 47 | 48 | private boolean mUnavailable; 49 | 50 | private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 51 | @Override 52 | public void onReceive(Context context, Intent intent) { 53 | if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 54 | int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 55 | mUnavailable = state != BluetoothAdapter.STATE_ON; 56 | 57 | switch (state) { 58 | case BluetoothAdapter.STATE_ON: 59 | saveInitialValues(); 60 | case BluetoothAdapter.STATE_OFF: 61 | fillLists(); 62 | break; 63 | default: 64 | } 65 | } 66 | } 67 | }; 68 | 69 | @Override 70 | public void onCreate(Bundle savedInstanceState) { 71 | super.onCreate(savedInstanceState); 72 | 73 | mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 74 | mAdapter = BluetoothAdapter.getDefaultAdapter(); 75 | mDeviceClassRepository = new BppDeviceClassRepository(getContext()); 76 | 77 | mUnavailable = mAdapter == null || !mAdapter.isEnabled(); 78 | 79 | if (mUnavailable) { 80 | registerForActivityResult( 81 | new ActivityResultContracts.StartActivityForResult(), 82 | result -> { 83 | if (result.getResultCode() == RESULT_OK) { 84 | saveInitialValues(); 85 | fillLists(); 86 | } 87 | }).launch(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)); 88 | } else { 89 | saveInitialValues(); 90 | } 91 | 92 | fillLists(); 93 | } 94 | 95 | @Override 96 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 97 | addPreferencesFromResource(R.xml.fragment_bpp_main); 98 | } 99 | 100 | @Override 101 | public void onResume() { 102 | super.onResume(); 103 | 104 | FragmentActivity activity = getActivity(); 105 | if (activity != null) { 106 | activity.registerReceiver(mReceiver, mIntentFilter); 107 | } 108 | 109 | fillLists(); 110 | } 111 | 112 | @Override 113 | public void onPause() { 114 | super.onPause(); 115 | 116 | FragmentActivity activity = getActivity(); 117 | if (activity != null) { 118 | activity.unregisterReceiver(mReceiver); 119 | } 120 | } 121 | 122 | @Override 123 | public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { 124 | super.onCreateOptionsMenu(menu, inflater); 125 | 126 | menu.add(0, MENU_DEVICE_CLASS_NEW, 0, 127 | getResources().getString(R.string.menu_device_class_new)) 128 | .setIcon(R.drawable.ic_add_24); 129 | } 130 | 131 | @Override 132 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 133 | switch (item.getItemId()) { 134 | case MENU_DEVICE_CLASS_NEW: 135 | addNewDeviceClass(); 136 | return true; 137 | default: 138 | return super.onOptionsItemSelected(item); 139 | } 140 | } 141 | 142 | @Override 143 | public boolean onPreferenceChange(Preference preference, Object newValue) { 144 | Log.d(TAG, "onPreferenceChange(): Preference - " + preference 145 | + ", newValue - " + newValue + ", newValue type - " 146 | + newValue.getClass()); 147 | 148 | if (newValue instanceof String) { 149 | BppDeviceClassColumns newDeviceClass = mDeviceClassRepository.get(Integer.parseInt((String) newValue)); 150 | if (newDeviceClass != null) { 151 | return mAdapter.setBluetoothClass(new BluetoothClass(newDeviceClass.getValue())); 152 | } 153 | } 154 | 155 | return true; 156 | } 157 | 158 | private void saveInitialValues() { 159 | BppDeviceClassColumns defaultClass = mDeviceClassRepository.getDefault(); 160 | Log.d(TAG, "saveInitialValues(): defaultClass - " + defaultClass); 161 | if (defaultClass == null) { 162 | BluetoothClass bluetoothClass = mAdapter.getBluetoothClass(); 163 | Log.d(TAG, "saveInitialValues(): bluetoothClass - " + bluetoothClass); 164 | mDeviceClassRepository.saveDefault(new BppDeviceClassColumns( 165 | "Default", 166 | bluetoothClass != null ? 167 | bluetoothClass.getClassOfDevice() : 168 | FALLBACK_DEFAULT_BLUETOOTH_DEVICE_CLASS 169 | )); 170 | } 171 | } 172 | 173 | private void fillLists() { 174 | List codDataList = mDeviceClassRepository.getAll(); 175 | Log.d(TAG, "fillLists(): codDataList - " + codDataList); 176 | 177 | if (!codDataList.isEmpty()) { 178 | final PreferenceGroup codPrefList = findPreference("device_class_list"); 179 | 180 | if (codPrefList != null) { 181 | codPrefList.removeAll(); 182 | 183 | BluetoothClass bluetoothClass = mAdapter.getBluetoothClass(); 184 | 185 | for (BppDeviceClassColumns codData : codDataList) { 186 | final BppDeviceClassPreference pref = new BppDeviceClassPreference(getContext()); 187 | 188 | pref.setKey(Integer.toString(codData.getId())); 189 | pref.setTitle(codData.getName()); 190 | pref.setPersistent(false); 191 | pref.setSelectable(mAdapter.isEnabled()); 192 | pref.setOnPreferenceChangeListener(this); 193 | pref.setIconSpaceReserved(false); 194 | 195 | pref.setSummary(BppUtils.formatDeviceClass(codData.getValue())); 196 | 197 | Log.d(TAG, "fillLists(): codData.getValue - " + codData.getValue() 198 | + " deviceClass - " + (bluetoothClass != null ? bluetoothClass.getClassOfDevice() : null)); 199 | 200 | if (bluetoothClass != null && codData.getValue() == bluetoothClass.getClassOfDevice()) { 201 | pref.setChecked(); 202 | } else if (bluetoothClass == null && codData.getValue() == FALLBACK_DEFAULT_BLUETOOTH_DEVICE_CLASS) { 203 | pref.setChecked(); 204 | } 205 | 206 | codPrefList.addPreference(pref); 207 | } 208 | } 209 | } 210 | } 211 | 212 | private void addNewDeviceClass() { 213 | Intent intent = new Intent(getContext(), BppDeviceClassEditorActivity.class); 214 | intent.setAction(ACTION_DEVICE_CLASS_INSERT); 215 | intent.putExtra(URI_EXTRA, BppDeviceClassContentProvider.URI); 216 | startActivity(intent); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/preference/BppBasePreference.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.preference; 2 | 3 | import android.content.ContentUris; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.widget.CompoundButton; 11 | import android.widget.RadioButton; 12 | import androidx.preference.Preference; 13 | import androidx.preference.PreferenceViewHolder; 14 | import com.github.teamjcd.bpp.R; 15 | import com.github.teamjcd.bpp.activity.BppBaseActivity; 16 | 17 | import static com.github.teamjcd.bpp.fragment.BppBaseEditorFragment.URI_EXTRA; 18 | 19 | public abstract class BppBasePreference extends Preference implements CompoundButton.OnCheckedChangeListener { 20 | private final static String TAG = BppBasePreference.class.getName(); 21 | 22 | private String mSelectedKey = null; 23 | private CompoundButton mCurrentChecked = null; 24 | private boolean mProtectFromCheckedChange = false; 25 | private boolean mSelectable = true; 26 | 27 | public BppBasePreference(Context context, AttributeSet attrs, int defStyleAttr) { 28 | super(context, attrs, defStyleAttr); 29 | setWidgetLayoutResource(R.layout.widget_bpp_selectable); 30 | } 31 | 32 | public BppBasePreference(Context context, AttributeSet attrs) { 33 | this(context, attrs, androidx.preference.R.attr.preferenceStyle); 34 | } 35 | 36 | public BppBasePreference(Context context) { 37 | this(context, null); 38 | } 39 | 40 | @Override 41 | public void onBindViewHolder(PreferenceViewHolder view) { 42 | super.onBindViewHolder(view); 43 | 44 | View widget = view.findViewById(R.id.widget_bpp_selectable_radiobutton); 45 | 46 | if (widget instanceof RadioButton) { 47 | RadioButton rb = (RadioButton) widget; 48 | 49 | if (mSelectable) { 50 | rb.setOnCheckedChangeListener(this); 51 | 52 | boolean isChecked = getKey().equals(mSelectedKey); 53 | if (isChecked) { 54 | mCurrentChecked = rb; 55 | mSelectedKey = getKey(); 56 | } 57 | 58 | mProtectFromCheckedChange = true; 59 | rb.setChecked(isChecked); 60 | 61 | mProtectFromCheckedChange = false; 62 | rb.setVisibility(View.VISIBLE); 63 | } else { 64 | rb.setVisibility(View.GONE); 65 | } 66 | } 67 | } 68 | 69 | @Override 70 | protected void onClick() { 71 | super.onClick(); 72 | 73 | Context context = getContext(); 74 | if (context != null) { 75 | int pos = Integer.parseInt(getKey()); 76 | Uri url = ContentUris.withAppendedId(getContentUri(), pos); 77 | Intent editIntent = new Intent(getContext(), getIntentClass()); 78 | editIntent.setAction(getAction()); 79 | editIntent.putExtra(URI_EXTRA, url); 80 | context.startActivity(editIntent); 81 | } 82 | } 83 | 84 | public void setChecked() { 85 | mSelectedKey = getKey(); 86 | } 87 | 88 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 89 | Log.i(TAG, "ID: " + getKey() + " :" + isChecked); 90 | 91 | if (mProtectFromCheckedChange) { 92 | return; 93 | } 94 | 95 | if (isChecked) { 96 | if (mCurrentChecked != null) { 97 | mCurrentChecked.setChecked(false); 98 | } 99 | 100 | mCurrentChecked = buttonView; 101 | mSelectedKey = getKey(); 102 | callChangeListener(mSelectedKey); 103 | } else { 104 | mCurrentChecked = null; 105 | mSelectedKey = null; 106 | } 107 | } 108 | 109 | public void setSelectable(boolean selectable) { 110 | mSelectable = selectable; 111 | } 112 | 113 | protected abstract Uri getContentUri(); 114 | 115 | protected abstract Class getIntentClass(); 116 | 117 | protected abstract String getAction(); 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/preference/BppDeviceClassPreference.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.preference; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import com.github.teamjcd.bpp.activity.BppBaseActivity; 6 | import com.github.teamjcd.bpp.activity.BppDeviceClassEditorActivity; 7 | 8 | import static com.github.teamjcd.bpp.content.BppDeviceClassContentProvider.URI; 9 | import static com.github.teamjcd.bpp.fragment.BppMainFragment.ACTION_DEVICE_CLASS_EDIT; 10 | 11 | public class BppDeviceClassPreference extends BppBasePreference { 12 | public BppDeviceClassPreference(Context context) { 13 | super(context); 14 | } 15 | 16 | @Override 17 | protected Uri getContentUri() { 18 | return URI; 19 | } 20 | 21 | @Override 22 | protected Class getIntentClass() { 23 | return BppDeviceClassEditorActivity.class; 24 | } 25 | 26 | @Override 27 | protected String getAction() { 28 | return ACTION_DEVICE_CLASS_EDIT; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/provider/BppBaseColumns.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.provider; 2 | 3 | import android.provider.BaseColumns; 4 | 5 | public abstract class BppBaseColumns implements BaseColumns { 6 | private int id; 7 | private String name; 8 | private int value; 9 | private int isDefault; 10 | 11 | public BppBaseColumns(String name, int value) { 12 | setName(name); 13 | setValue(value); 14 | } 15 | 16 | public BppBaseColumns(int id, String name, int value, int isDefault) { 17 | this(name, value); 18 | setId(id); 19 | setIsDefault(isDefault); 20 | } 21 | 22 | public BppBaseColumns() { 23 | } 24 | 25 | public int getId() { 26 | return id; 27 | } 28 | 29 | public void setId(int id) { 30 | this.id = id; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | public int getValue() { 42 | return value; 43 | } 44 | 45 | public void setValue(int value) { 46 | this.value = value; 47 | } 48 | 49 | public int getIsDefault() { 50 | return isDefault; 51 | } 52 | 53 | public void setIsDefault(int isDefault) { 54 | this.isDefault = isDefault; 55 | } 56 | 57 | public boolean isDefault() { 58 | return isDefault == 1; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/provider/BppDeviceClassColumns.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.provider; 2 | 3 | public class BppDeviceClassColumns extends BppBaseColumns { 4 | public BppDeviceClassColumns(String name, int value) { 5 | super(name, value); 6 | } 7 | 8 | public BppDeviceClassColumns() { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/repository/BppBaseRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.repository; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import com.github.teamjcd.bpp.provider.BppBaseColumns; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.function.Supplier; 12 | 13 | import static android.provider.BaseColumns._ID; 14 | import static com.github.teamjcd.bpp.content.BppBaseContentProvider.DEFAULT_URI; 15 | import static com.github.teamjcd.bpp.content.BppDeviceClassContentProvider.URI; 16 | import static com.github.teamjcd.bpp.database.BppDatabaseHelper.*; 17 | 18 | public abstract class BppBaseRepository { 19 | private final Context context; 20 | private final Supplier newInstance; 21 | 22 | public BppBaseRepository(Context context, Supplier newInstance) { 23 | this.context = context; 24 | this.newInstance = newInstance; 25 | } 26 | 27 | public List getAll() { 28 | Cursor cursor = context.getContentResolver().query( 29 | getUri(), 30 | PROJECTION, 31 | null, //selection 32 | null, //selectionArgs 33 | null //sortOrder 34 | ); 35 | 36 | List result = new ArrayList<>(); 37 | if (cursor.getCount() > 0) { 38 | cursor.moveToFirst(); 39 | do { 40 | result.add(readFromCursor(cursor)); 41 | } while (cursor.moveToNext()); 42 | } 43 | cursor.close(); 44 | return result; 45 | } 46 | 47 | public T get(int id) { 48 | return get(Uri.withAppendedPath(getUri(), String.valueOf(id))); 49 | } 50 | 51 | public T getDefault() { 52 | return get(Uri.withAppendedPath(getUri(), DEFAULT_URI)); 53 | } 54 | 55 | public T get(Uri uri) { 56 | Cursor cursor = context.getContentResolver().query( 57 | uri, 58 | PROJECTION, 59 | null, 60 | null, 61 | _ID 62 | ); 63 | if (cursor == null) { 64 | return null; 65 | } 66 | try { 67 | return getFromCursor(cursor); 68 | } finally { 69 | cursor.close(); 70 | } 71 | } 72 | 73 | private T getFromCursor(Cursor cursor) { 74 | if (cursor.getCount() > 0) { 75 | cursor.moveToFirst(); 76 | return readFromCursor(cursor); 77 | } else { 78 | return null; 79 | } 80 | } 81 | 82 | @SuppressWarnings("UnusedReturnValue") 83 | public Uri saveDefault(T columns) { 84 | columns.setIsDefault(1); 85 | return save(columns); 86 | } 87 | 88 | public Uri save(T columns) { 89 | ContentValues values = toContentValues(columns); 90 | return context.getContentResolver().insert(URI, values); 91 | } 92 | 93 | @SuppressWarnings("UnusedReturnValue") 94 | public int update(T columns) { 95 | return update(columns.getId(), columns); 96 | } 97 | 98 | public int update(int id, T columns) { 99 | return update(Uri.withAppendedPath(getUri(), String.valueOf(id)), columns); 100 | } 101 | 102 | public int update(Uri uri, T columns) { 103 | return context.getContentResolver().update( 104 | uri, 105 | toContentValues(columns), 106 | null, 107 | null 108 | ); 109 | } 110 | 111 | @SuppressWarnings("UnusedReturnValue") 112 | public int delete(int id) { 113 | return delete(Uri.withAppendedPath(getUri(), Integer.toString(id))); 114 | } 115 | 116 | public int delete(Uri uri) { 117 | return context.getContentResolver().delete( 118 | uri, 119 | null, 120 | null 121 | ); 122 | } 123 | 124 | private T readFromCursor(Cursor cursor) { 125 | T result = newInstance.get(); 126 | result.setId(cursor.getInt(INDEX_ID)); 127 | result.setName(cursor.getString(INDEX_NAME)); 128 | result.setValue(cursor.getInt(INDEX_VALUE)); 129 | result.setIsDefault(cursor.getInt(INDEX_IS_DEFAULT)); 130 | return result; 131 | } 132 | 133 | private ContentValues toContentValues(T columns) { 134 | ContentValues values = new ContentValues(); 135 | values.put(COLUMN_NAME, columns.getName()); 136 | values.put(COLUMN_VALUE, columns.getValue()); 137 | values.put(COLUMN_IS_DEFAULT, columns.getIsDefault()); 138 | return values; 139 | } 140 | 141 | protected abstract Uri getUri(); 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/repository/BppDeviceClassRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.repository; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import com.github.teamjcd.bpp.provider.BppDeviceClassColumns; 6 | 7 | import static com.github.teamjcd.bpp.content.BppDeviceClassContentProvider.URI; 8 | 9 | public class BppDeviceClassRepository extends BppBaseRepository { 10 | public BppDeviceClassRepository(Context context) { 11 | super(context, BppDeviceClassColumns::new); 12 | } 13 | 14 | @Override 15 | protected Uri getUri() { 16 | return URI; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/teamjcd/bpp/util/BppUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.teamjcd.bpp.util; 2 | 3 | public abstract class BppUtils { 4 | public static int parseHex(String hex) throws NumberFormatException { 5 | return Integer.parseInt(hex, 16); 6 | } 7 | 8 | public static String formatDeviceClass(int deviceClass) { 9 | String deviceClassHex = Integer.toHexString(deviceClass); 10 | return ("000000" + deviceClassHex).substring(deviceClassHex.length()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_bpp_device_class_editor.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_bpp_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/widget_bpp_selectable.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 27 | 28 | 37 | 38 | 39 | 40 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamJCD/BluetoothPlusPlus/3ac9bf98fb44f2dfa392a866f3f1342df71ea442/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamJCD/BluetoothPlusPlus/3ac9bf98fb44f2dfa392a866f3f1342df71ea442/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamJCD/BluetoothPlusPlus/3ac9bf98fb44f2dfa392a866f3f1342df71ea442/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamJCD/BluetoothPlusPlus/3ac9bf98fb44f2dfa392a866f3f1342df71ea442/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamJCD/BluetoothPlusPlus/3ac9bf98fb44f2dfa392a866f3f1342df71ea442/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-ar/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | لا يمكن أن يكون حقل فئة الجهاز فارغًا. 4 | فئة الجهاز غير صالحة. 5 | لا يمكن أن يكون حقل الاسم فارغًا. 6 | 7 | New Device Class 8 | حفظ 9 | تجاهل 10 | حذف 11 | 12 | فئة الجهاز 13 | الاسم 14 | 15 | تحرير فئة الجهاز 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Das Namensfeld darf nicht leer sein. 5 | Ungültige Geräteklasse. 6 | Das Geräteklasse-Feld darf nicht leer sein. 7 | 8 | Neue Geräteklasse 9 | Speichern 10 | Verwerfen 11 | Löschen 12 | 13 | Geräteklasse 14 | Name 15 | 16 | Geräteklasse bearbeiten 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | El campo de Clase de Dispositivo no puede estar vacío. 4 | Clase de Dispositivo invalida. 5 | El campo de Nombre no puede estar vacío. 6 | 7 | New Device Class 8 | Guardar 9 | Cancelar 10 | Borrar 11 | 12 | Clase de Dispositivo 13 | Nombre 14 | 15 | Editar Clase de Dispositivo 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Le champ Classe de l\'appareil ne peut pas être vide. 4 | Classe de l\'appareil invalide. 5 | Le champ Nom ne peut pas être vide. 6 | 7 | New Device Class 8 | Enregistrer 9 | Annuler 10 | Supprimer 11 | 12 | Classe de l\'appareil 13 | Nom 14 | 15 | Modifier la classe de l\'appareil 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Il campo "Classe Dispositivo" non può essere vuoto. 5 | Classe Dispositivo sconosciuta. 6 | Il campo "Nome" non può essere vuoto. 7 | 8 | New Device Class 9 | Salva 10 | Annulla 11 | Elimina 12 | 13 | Classe Dispositivo 14 | Nome 15 | 16 | Modifica Classe Dispositivo 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @color/white 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |