Skip to content

Commit

Permalink
Push poc usable app
Browse files Browse the repository at this point in the history
  • Loading branch information
sultanm authored and sultanm committed Aug 10, 2023
1 parent fce53d3 commit aed8db7
Show file tree
Hide file tree
Showing 23 changed files with 637 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
.idea/**
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Firetv volume control

In POC(Proof of concept) state - using it in my current fire tv lite, feel free to try it out

This is a second draft, first draft was without UI

## Usage:

Install apk and then grant below permissions from adb

1. To be able to listen to key presses on remote
2. To be able to show the volume controls UI

```shell
adb shell settings put secure enabled_accessibility_services com.example.volumecontrolservice/com.example.volumecontrolservice.AccessibilityService
adb shell appops set com.example.volumecontrolservice SYSTEM_ALERT_WINDOW allow
```
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
29 changes: 29 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id 'com.android.application'
}

android {
namespace 'com.example.volumecontrolservice'
compileSdk 32

defaultConfig {
applicationId "com.example.volumecontrolservice"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"

}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {

implementation 'androidx.leanback:leanback:1.0.0'
}
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
31 changes: 31 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

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

<uses-permission
android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
tools:ignore="ProtectedPermissions" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.VolumeControlService" >

<service
android:exported="true"
android:name=".AccessibilityService"
android:label="Volume control service"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service" />
</service>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package com.example.volumecontrolservice;

import static android.media.AudioManager.STREAM_MUSIC;
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.widget.Toast.LENGTH_SHORT;

import android.app.Dialog;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ProgressBar;
import android.widget.Toast;

public class AccessibilityService extends android.accessibilityservice.AccessibilityService {
// settings put secure enabled_accessibility_services com.example.volumecontrolservice/com.example.volumecontrolservice.AccessibilityService
private static final String TAG = "yolo volume_control";
private static final long DELAY_VOLUME_LONG_PRESS = 100;
private Handler mLongPressHandler;
private AudioManager audioManager;
private boolean eatLongPressActionUp;

@Override
protected void onServiceConnected() {
super.onServiceConnected();
Log.i(TAG, "service is connected");
audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
}

@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) { }

@Override
public void onInterrupt() {

}

@Override
protected boolean onKeyEvent(KeyEvent event) {
return handleKeyEvent(event);
}

private boolean handleKeyEvent(KeyEvent event) {
int action = event.getAction();
int keyCode = event.getKeyCode();
if (keyCode == KEYCODE_DPAD_CENTER) {
switch (action) {
case ACTION_DOWN:
Log.i(TAG, "yolo dpad center is down ");
checkForLongPress();
return false;
case ACTION_UP:
if(mLongPressHandler !=null ) mLongPressHandler.removeCallbacksAndMessages(null);
if(eatLongPressActionUp){
eatLongPressActionUp = false;
return true;
}
return false;
}
}
return false;
}

private void checkForLongPress() {
if (mLongPressHandler == null) {
mLongPressHandler = new Handler();
}
else {
// this shouldn't be happening, down event coming before previous up event is processed
mLongPressHandler.removeCallbacksAndMessages(null);
mLongPressHandler = new Handler();
}

mLongPressHandler.postDelayed(() -> mLongPressHandler.postDelayed(() -> {
// Toast.makeText(this, "Adjust volume then press okay to resume", LENGTH_SHORT).show();
Log.i(TAG, "ready to adjust volume");
showDialog();
eatLongPressActionUp = true;
}, DELAY_VOLUME_LONG_PRESS), DELAY_VOLUME_LONG_PRESS);
}

private void showDialog() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
final boolean overlayEnabled = Settings.canDrawOverlays(this);
if(!overlayEnabled){
Toast.makeText(this, "Enable draw over apps permission", LENGTH_SHORT).show();
}
Log.i(TAG, "overlay: " + overlayEnabled);
if (!overlayEnabled) return;
}

new Handler(Looper.getMainLooper()).post(() -> {

final Dialog dialog = new Dialog(AccessibilityService.this);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.volume_control);
dialog.getWindow().setBackgroundDrawableResource(android.R.color.background_dark);
dialog.setCancelable(true);
ProgressBar volumeProgress = dialog.findViewById(R.id.volume_progress);
dialog.findViewById(R.id.close).setOnClickListener(v -> dialog.cancel());
volumeProgress.setMax(audioManager.getStreamMaxVolume(STREAM_MUSIC));
volumeProgress.setProgress(audioManager.getStreamVolume(STREAM_MUSIC));
dialog.setOnKeyListener((dialog1, keyCode, event) -> {
int action = event.getAction();
if (keyCode == KEYCODE_DPAD_LEFT || keyCode == KEYCODE_DPAD_DOWN) {
switch (action) {
case ACTION_DOWN:
Log.i(TAG, "yolo dpad down or left is pressed");
reduceVolume();
volumeProgress.setProgress(audioManager.getStreamVolume(STREAM_MUSIC));
return true;
case ACTION_UP:
return true;
}
}

if (keyCode == KEYCODE_DPAD_RIGHT || keyCode == KEYCODE_DPAD_UP) {
switch (action) {
case ACTION_DOWN:
Log.i(TAG, "yolo dpad up or right is pressed");
increaseVolume();
volumeProgress.setProgress(audioManager.getStreamVolume(STREAM_MUSIC));
return true;
case ACTION_UP:
return true;
}
}

return false;
});

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}else{
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}

dialog.show();
});
}

private void increaseVolume() {
Log.i(TAG, "yolo, increasing volume");
audioManager.adjustStreamVolume(STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
}

private void reduceVolume() {
Log.i(TAG, "yolo, reducing volume");
audioManager.adjustStreamVolume(STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0);
}
}
30 changes: 30 additions & 0 deletions app/src/main/res/layout/volume_control.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_alignParentTop="true"
android:id="@+id/text"
android:text="Volume:"
android:layout_centerVertical="true"
android:textColor="@color/lb_tv_white"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/volume_progress"
android:layout_marginStart="30dp"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/text"
style="@android:style/Widget.ProgressBar.Horizontal"
android:progress="50"
android:layout_width="200dp"
android:layout_height="10dp" />
<Button
android:layout_alignParentEnd="true"
android:layout_marginStart="30dp"
android:id="@+id/close"
android:text="Close"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
Binary file added app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary file not shown.
Binary file added app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary file not shown.
Binary file added app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary file not shown.
Binary file added app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary file not shown.
Binary file added app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary file not shown.
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">VolumeControlService</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<resources>

<style name="Theme.VolumeControlService" parent="@style/Theme.Leanback" />
</resources>
5 changes: 5 additions & 0 deletions app/src/main/res/xml/accessibility_service.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_name"
android:canRequestFilterKeyEvents="true"
android:accessibilityFlags="flagRequestFilterKeyEvents"/>
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
}
21 changes: 21 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
6 changes: 6 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#Sun Aug 06 01:20:14 IST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Loading

0 comments on commit aed8db7

Please sign in to comment.