Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rostopira committed Jun 16, 2024
0 parents commit 2a16e47
Showing 31 changed files with 713 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/migrations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# BT QS

Google changed Bluetooth QS tile in Android 14.
I've been asked to make same simple tile as my previous [WiFi QS](https://github.com/rostopira/wifi_qs) project.

Target API 30 is required to allow this app to turn Bluetooth on and off without extra permission.
I can't publish this in Google Play, since it requires minimum target API 33.

# FAQ

## I want Wi-Fi tile

Check out [WiFi QS](https://github.com/rostopira/wifi_qs).

## I want internet toggle

Nope, not possible without root.

## I have root

Use [Better Internet Tiles](https://play.google.com/store/apps/details?id=be.casperverswijvelt.unifiedinternetqs)

## I don't have root, what about Shizuku

Use [Better Internet Tiles](https://play.google.com/store/apps/details?id=be.casperverswijvelt.unifiedinternetqs)

I'm not going to rewrite it using shizuku

## I have feature request

I don't have time for that. PR's are welcome
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
32 changes: 32 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
plugins {
alias(libs.plugins.android.application)
}

android {
namespace 'dev.rostopira.btqs'
compileSdk 34

defaultConfig {
applicationId "dev.rostopira.btqs"
minSdk 30
//noinspection ExpiredTargetSdkVersion
targetSdk 30
versionCode 1
versionName "1.0"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_9
targetCompatibility JavaVersion.VERSION_1_9
}
}

dependencies {
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
40 changes: 40 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">

<uses-feature
android:name="android.hardware.bluetooth"
android:required="true"/>

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<application
android:allowBackup="true"
android:label="BT QS"
android:supportsRtl="false"
tools:ignore="MissingApplicationIcon">

<activity
android:name=".LongPressReceiver"
android:excludeFromRecents="true"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
</intent-filter>
</activity>

<service
android:name=".BtTileService"
android:label="BT QS"
android:icon="@drawable/connected"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>

</application>

</manifest>
7 changes: 7 additions & 0 deletions app/src/main/java/dev/rostopira/btqs/BtState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.rostopira.btqs;

public enum BtState {
DISABLED,
ENABLED,
CONNECTED,
}
5 changes: 5 additions & 0 deletions app/src/main/java/dev/rostopira/btqs/BtStateListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package dev.rostopira.btqs;

public interface BtStateListener {
void onBtStateChanged(BtState state, String deviceName);
}
60 changes: 60 additions & 0 deletions app/src/main/java/dev/rostopira/btqs/BtStateReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dev.rostopira.btqs;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;

import java.lang.reflect.Method;
import java.util.Set;

public class BtStateReceiver extends BroadcastReceiver {
final BtStateListener listener;

BtStateReceiver(BtStateListener listener) {
this.listener = listener;
}

@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (!adapter.isEnabled()) {
listener.onBtStateChanged(BtState.DISABLED, null);
return;
}
final Set<BluetoothDevice> paired = adapter.getBondedDevices();
for (BluetoothDevice device: paired) {
if (isConnected(device)) {
listener.onBtStateChanged(BtState.CONNECTED, device.getAlias());
return;
}
}
listener.onBtStateChanged(BtState.ENABLED, null);
}

static BtStateReceiver createAndRegister(Context context, BtStateListener listener) {
final BtStateReceiver receiver = new BtStateReceiver(listener);
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
context.registerReceiver(receiver, intentFilter);
return receiver;
}

private static boolean isConnected(BluetoothDevice device) {
try {
//noinspection JavaReflectionMemberAccess
final Method method = device.getClass().getMethod("isConnected");
//noinspection DataFlowIssue
return (Boolean) method.invoke(device);
} catch (Exception e) {
Log.e("BtStateReceiver", "Failed to check connection state", e);
return false;
}
}
}
69 changes: 69 additions & 0 deletions app/src/main/java/dev/rostopira/btqs/BtTileService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dev.rostopira.btqs;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.graphics.drawable.Icon;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;

public class BtTileService extends TileService implements BtStateListener {
BtStateReceiver btStateReceiver;

@Override
public void onStartListening() {
super.onStartListening();
if (btStateReceiver != null) {
return;
}
btStateReceiver = BtStateReceiver.createAndRegister(this, this);
btStateReceiver.onReceive(this, null);
}

@Override
public void onBtStateChanged(BtState state, String deviceName) {
final Tile tile = getQsTile();
if (tile == null) {
return;
}
tile.setLabel("Bluetooth");
switch (state) {
case DISABLED:
tile.setIcon(Icon.createWithResource(this, R.drawable.disabled));
tile.setSubtitle(getString(R.string.off));
tile.setState(Tile.STATE_INACTIVE);
break;
case ENABLED:
tile.setIcon(Icon.createWithResource(this, R.drawable.enabled));
tile.setSubtitle(getString(R.string.disconnected));
tile.setState(Tile.STATE_ACTIVE);
break;
case CONNECTED:
tile.setIcon(Icon.createWithResource(this, R.drawable.connected));
tile.setSubtitle(deviceName != null ? deviceName : getString(R.string.connected));
tile.setState(Tile.STATE_ACTIVE);
break;
}
tile.updateTile();
}

@SuppressLint("MissingPermission")
@Override
public void onClick() {
super.onClick();
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.isEnabled()) {
adapter.disable();
} else {
adapter.enable();
}
}

@Override
public void onStopListening() {
super.onStopListening();
if (btStateReceiver != null) {
unregisterReceiver(btStateReceiver);
btStateReceiver = null;
}
}
}
19 changes: 19 additions & 0 deletions app/src/main/java/dev/rostopira/btqs/LongPressReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.rostopira.btqs;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;

public class LongPressReceiver extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
}

}
3 changes: 3 additions & 0 deletions app/src/main/res/drawable/connected.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960">
<path android:fillColor="#FFF" android:pathData="M440,880v-304L256,760l-56,-56 224,-224 -224,-224 56,-56 184,184v-304h40l228,228 -172,172 172,172L480,880h-40ZM520,384 L596,308 520,234v150ZM520,726 L596,652 520,576v150ZM200,540q-25,0 -42.5,-17.5T140,480q0,-25 17.5,-42.5T200,420q25,0 42.5,17.5T260,480q0,25 -17.5,42.5T200,540ZM760,540q-25,0 -42.5,-17.5T700,480q0,-25 17.5,-42.5T760,420q25,0 42.5,17.5T820,480q0,25 -17.5,42.5T760,540Z" />
</vector>
Loading

0 comments on commit 2a16e47

Please sign in to comment.