Skip to content

Commit

Permalink
Build 1.4.0 : Added SoundMaster!
Browse files Browse the repository at this point in the history
  • Loading branch information
legendsayantan committed Jun 8, 2024
1 parent 5e356b1 commit 4e42c97
Show file tree
Hide file tree
Showing 27 changed files with 1,303 additions and 30 deletions.
10 changes: 10 additions & 0 deletions .idea/deploymentTargetDropDown.xml

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

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

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

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ Contains some easy-to-use tools to go beyond the level of control allowed by And
### Tools -
- [x] **Debloater** - Uninstall system apps and bloatware, with app information from [UAD](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation).
- [x] **ThemePatcher** - Unlocks premium content for free, from the Oppo/Realme/Oneplus theme store.
- [x] **LookBack** - Allows downgrade of apps, without uninstallation.
- [x] **MixedAudio** - Allows multiple media apps to play at the same time, or mute audio of specific apps.
- [x] **SoundMaster** - Independent volume control for every app, and more! Requires Android 10 or later.
⚠ SoundMaster may not work on apps with strong copyright protection, like Spotify. In case SoundMaster crashes and some apps lose sound output, use MixedAudio to unmute them.
- [x] **LookBack** - Allows downgrade of apps, without uninstallation.
- [x] **ADB Shell** - Manually execute other raw ADB commands.
- [x] **Intent Shell** - Allows other apps (Tasker,MacroDroid,etc) to run ADB commands via intent requests.

Expand Down
3 changes: 2 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ android {
minSdk 27
targetSdk 34
versionCode 1
versionName "1.3.0"
versionName "1.4.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -40,6 +40,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.activity:activity:1.9.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
Expand Down
Binary file modified app/release/app-release.apk
Binary file not shown.
22 changes: 19 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
Expand All @@ -25,17 +29,29 @@
android:supportsRtl="true"
android:theme="@style/Theme.AdbTools"
tools:targetApi="31">
<service
android:name=".services.SoundMasterService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="mediaProjection" />

<receiver
android:name=".receivers.IntentReceiver"
android:label="Intent Shell"
android:enabled="true"
android:exported="true">
android:exported="true"
android:label="Intent Shell">
<intent-filter>
<action android:name="com.legendsayantan.adbtools.execute" />
<category android:name="android.intent.category.DEFAULT"/>

<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>

<activity
android:name=".SoundMasterActivity"
android:exported="false"
android:label=""
android:theme="@style/DialogActivityTheme" />
<activity
android:name=".MixedAudioActivity"
android:exported="false" />
Expand Down
41 changes: 40 additions & 1 deletion app/src/main/java/com/legendsayantan/adbtools/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.legendsayantan.adbtools

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.Gravity
Expand All @@ -14,10 +18,12 @@ import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
Expand All @@ -26,6 +32,7 @@ import com.legendsayantan.adbtools.lib.Utils.Companion.initialiseStatusBar
import java.util.UUID

class MainActivity : AppCompatActivity() {
@SuppressLint("LaunchActivityFromNotification")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Expand All @@ -43,6 +50,7 @@ class MainActivity : AppCompatActivity() {
val cardThemePatcher = findViewById<MaterialCardView>(R.id.cardThemePatcher)
val cardLookBack = findViewById<MaterialCardView>(R.id.cardLookBack)
val cardMixedAudio = findViewById<MaterialCardView>(R.id.cardMixedAudio)
val cardSoundMaster = findViewById<MaterialCardView>(R.id.cardSoundMaster)
val cardShell = findViewById<MaterialCardView>(R.id.cardShell)
val cardIntentShell = findViewById<MaterialCardView>(R.id.cardIntentShell)
cardDebloat.setOnClickListener {
Expand Down Expand Up @@ -77,6 +85,37 @@ class MainActivity : AppCompatActivity() {
)
)
}
cardSoundMaster.setOnClickListener {
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q) return@setOnClickListener
//create notification
val intent = Intent(this, SoundMasterActivity::class.java)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
val channelId = "notifications"
val notificationBuilder = NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.outline_info_24)
.setContentTitle("Tap to configure "+applicationContext.getString(R.string.soundmaster))
.setOngoing(true)
.setContentIntent(
PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)
.setPriority(NotificationCompat.PRIORITY_LOW)

// Show the notification.
with(NotificationManagerCompat.from(applicationContext)) {
if (ActivityCompat.checkSelfPermission(
applicationContext,
android.Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
notify(3, notificationBuilder.build())
}
}
}
cardShell.setOnClickListener { openShell() }
cardIntentShell.setOnClickListener { intentShell() }
}
Expand Down
193 changes: 193 additions & 0 deletions app/src/main/java/com/legendsayantan/adbtools/SoundMasterActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package com.legendsayantan.adbtools

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.media.projection.MediaProjectionManager
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import com.legendsayantan.adbtools.adapters.VolumeBarAdapter
import com.legendsayantan.adbtools.dialog.NewSliderDialog
import com.legendsayantan.adbtools.lib.ShizukuRunner
import com.legendsayantan.adbtools.lib.Utils.Companion.initialiseStatusBar
import com.legendsayantan.adbtools.services.SoundMasterService
import java.io.File
import java.io.FileNotFoundException
import java.util.Timer
import kotlin.concurrent.timerTask

class SoundMasterActivity : AppCompatActivity() {
private lateinit var mediaProjectionManager: MediaProjectionManager
val preferences by lazy {
applicationContext.getSharedPreferences(
"volumeplus",
Context.MODE_PRIVATE
)
}
var packages: MutableList<String>
get() = try {
File(applicationContext.filesDir, "soundmaster.txt").readText().split("\n")
.toMutableList()
} catch (f: FileNotFoundException) {
mutableListOf()
}
set(value) {
val file = File(applicationContext.filesDir, "soundmaster.txt")
if (!file.exists()) {
file.parentFile?.mkdirs()
file.createNewFile()
}
file.writeText(value.joinToString("\n"))
}

val volumeBarView by lazy { findViewById<RecyclerView>(R.id.volumeBars) }

@SuppressLint("ApplySharedPref")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sound_master)
initialiseStatusBar()
//new slider
findViewById<MaterialCardView>(R.id.newSlider).setOnClickListener {
NewSliderDialog(this@SoundMasterActivity) { pkg ->
val newPackages = packages
newPackages.add(pkg)
packages = newPackages
if (SoundMasterService.running) SoundMasterService.onDynamicAttach(pkg)
updateSliders()
}.show()
}

//outside touch
findViewById<ConstraintLayout>(R.id.main).setOnClickListener {
finish()
}
}

override fun onResume() {
updateBtnState()
updateSliders()
super.onResume()
}

fun updateBtnState() {
val btnImage = findViewById<ImageView>(R.id.playPauseButton)
btnImage.setImageResource(if (SoundMasterService.running) R.drawable.baseline_stop_24 else R.drawable.baseline_play_arrow_24)
btnImage.setOnClickListener {
val state = SoundMasterService.running
if (state) {
stopService(Intent(this, SoundMasterService::class.java))
} else if (packages.size > 0) {
if (packages.isEmpty()) {
Toast.makeText(
applicationContext,
"No apps selected to control",
Toast.LENGTH_SHORT
)
.show()
} else {
ShizukuRunner.runAdbCommand("pm grant ${baseContext.packageName} android.permission.RECORD_AUDIO",
object : ShizukuRunner.CommandResultListener {
override fun onCommandResult(output: String, done: Boolean) {
if (done) {
ShizukuRunner.runAdbCommand("appops set ${baseContext.packageName} PROJECT_MEDIA allow",
object : ShizukuRunner.CommandResultListener {
override fun onCommandResult(
output: String,
done: Boolean
) {
if (done) {
mediaProjectionManager =
applicationContext.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startActivityForResult(
mediaProjectionManager.createScreenCaptureIntent(),
MEDIA_PROJECTION_REQUEST_CODE
)
}
}
})
}
}
})
}
}
var count = 0
Timer().schedule(timerTask {
if (SoundMasterService.running != state) {
updateBtnState()
updateSliders()
cancel()
} else count++
if (count > 50) cancel()
}, 500, 500)
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == MEDIA_PROJECTION_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
Toast.makeText(
applicationContext,
"Controlling audio from selected apps",
Toast.LENGTH_SHORT
).show()
SoundMasterService.projectionData = data
startService(Intent(this, SoundMasterService::class.java).apply {
putExtra("packages", packages.toTypedArray())
})
} else {
Toast.makeText(
this, "Request to obtain MediaProjection denied.",
Toast.LENGTH_SHORT
).show()
}
}
}

private fun updateSliders() {
findViewById<TextView>(R.id.none).visibility =
if (packages.size > 0) View.GONE else View.VISIBLE
Thread {
val sliderMap = HashMap<String, Float>()
for (pkg in packages) {
val volume = SoundMasterService.getVolumeOf(pkg)
sliderMap[pkg] = volume
}
val adapter =
VolumeBarAdapter(this@SoundMasterActivity, sliderMap, { app, vol ->
SoundMasterService.setVolumeOf(app, vol)
}, {
val newPackages = packages
newPackages.remove(it)
packages = newPackages
updateSliders()
SoundMasterService.onDynamicDetach(it)
}, { app, sliderIndex ->
if (sliderIndex == 0) SoundMasterService.getBalanceOf(app)
else SoundMasterService.getBandValueOf(app, sliderIndex - 1)
}, { app, slider, value ->
if (slider == 0) SoundMasterService.setBalanceOf(app, value)
else SoundMasterService.setBandValueOf(app, slider - 1, value)
})
runOnUiThread {
volumeBarView.adapter = adapter
volumeBarView.invalidate()
}
}.start()
}

companion object {
private const val MEDIA_PROJECTION_REQUEST_CODE = 13
}
}
Loading

0 comments on commit 4e42c97

Please sign in to comment.