Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{"image":"mcr.microsoft.com/devcontainers/universal:2",
"features": {
"ghcr.io/devcontainers/features/java:latest": {
"installGradle": true,
"installMaven": true,
"installAnt": true,
"version": "17",
"gradleVersion": "8.8",
"jdkDistro": "oracle",
"gradleVersion": "latest",
"mavenVersion": "latest",
"antVersion": "latest"
},
"ghcr.io/devcontainers-contrib/features/meson-asdf:latest": {
"version": "latest"
},
"ghcr.io/balazs23/devcontainers-features/bazel:latest": {
"bazelisk": "latest"
},
"ghcr.io/akhildevelops/devcontainer-features/android-cli:latest": {
"PACKAGES": "platform-tools,platforms;android-34,build-tools;34.0.0"
}
}
}
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Main
run-name: "Build: ${{ github.actor }}: ${{ github.event.head_commit.message }}"

on: [push]
jobs:
Gradle:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup JDK
uses: actions/setup-java@v1
with:
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
dependency-graph: generate
# ^ [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit, clear]. The default value is 'disabled'.
# submit to where? upload to where?
#validate-wrappers: true
# ^ fails if missing in repo but using valid from system path :/
build-scan-publish: true
build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
build-scan-terms-of-use-agree: "yes"
- name: Gradle Build
run: gradle build -x test #--debug --stacktrace
# TODO: enable test, when we have any :(
#- name: Changelog If Tagged
# TODO: write git history to metadata/en-US/changelogs/{{ manifest versionCode value }}.txt
# TODO: bump manifest version on release?
- name: Release If Tagged
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: app/build/outputs/apk/debug/app-debug.apk

# on github™️ codespace™️ terminal, once:
# $ sdk use java 17.0.11-ms
# # chgrp codespace /opt/android -R
# # chmod g+w /opt/android -R
#
# $ gradle build -x test
41 changes: 30 additions & 11 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,41 +1,60 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
namespace "com.example.punksta.volumecontrol"

// Set as the same as targetSdkVersion
compileSdkVersion 34
buildToolsVersion '34.0.0'

defaultConfig {
applicationId "com.punksta.apps.volumecontrol"
minSdkVersion 15
targetSdkVersion 30
// minSdkVersion 15 // <-- to enable 15 again, need to figure out why Manifest.xml's tools:overrideLibrary is not working.
minSdkVersion 21
targetSdkVersion 34
versionCode 32
versionName "2.6.0"
versionName "2.6.1"
}

buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// ^ missing file?
}
}

compileOptions {
encoding = 'UTF-8'
// TODO: drop this 1_8?
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

lintOptions {
disable 'GoogleAppIndexingWarning'
showAll true
explainIssues true
textReport true
textOutput 'stdout'
}
}

def acraVersion = '5.7.0'
def appcompat_version = "1.3.1"

dependencies {
def appcompat_version = "1.7.0"
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation 'androidx.annotation:annotation:1.2.0'
testImplementation 'junit:junit:4.13'
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"

// no idea what uses kotlin, but without this:
// error: Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found...
// https://stackoverflow.com/a/75298544/183132
constraints {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") {
because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib")
}
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") {
because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib")
}
}
}
18 changes: 10 additions & 8 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.punksta.volumecontrol">
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />

<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<!--
TODO: tools:overrideLibrary="androidx.appcompat, androidx.appcompat.resources" so we can use minSdk=15 again?
-->

<application
android:name=".SoundApplication"
Expand All @@ -18,7 +24,7 @@
android:roundIcon="@mipmap/ic_launcher_new_round"
android:supportsRtl="false"
android:theme="@style/AppTheme">
<service android:name=".SoundService" />
<service android:name=".SoundService" android:foregroundServiceType="specialUse" />

<receiver
android:name=".BootReceiver"
Expand All @@ -32,20 +38,16 @@
</intent-filter>
</receiver>


<activity android:name=".EditProfileActivity" />
<activity
android:exported="true"
android:name=".MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>


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

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.widget.SwitchCompat;

import androidx.appcompat.app.AlertDialog;

Expand Down Expand Up @@ -53,6 +53,7 @@ public class MainActivity extends BaseActivity {
};

public static Intent createOpenProfileIntent(Context context, SoundProfile profile) {
DynamicShortcutManager.reportUsage(context, profile);
Intent intent1 = new Intent(context.getApplicationContext(), MainActivity.class);
intent1.setAction(Intent.ACTION_VIEW);
intent1.putExtra(PROFILE_ID, profile.id);
Expand Down Expand Up @@ -100,12 +101,12 @@ private boolean handleIntent(Intent intent) {

@Override
protected void onSaveInstanceState(Bundle outState) {

super.onSaveInstanceState(outState);
}

@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
//super.onSaveInstanceState(outState, outPersistentState);
super.onSaveInstanceState(outState, outPersistentState);
}

private void renderVolumeTypesInNotificationWidget() {
Expand Down Expand Up @@ -204,7 +205,7 @@ public void onChangeIndex(int audioType, int currentLevel, int max) {

private void buildUi() {

Switch s = findViewById(R.id.dark_theme_switcher);
SwitchCompat s = findViewById(R.id.dark_theme_switcher);

s.setChecked(isDarkTheme());
s.setOnCheckedChangeListener((buttonView, isChecked) -> setThemeAndRecreate(isChecked));
Expand All @@ -219,7 +220,7 @@ private void buildUi() {

renderProfileItems();

Switch s2 = findViewById(R.id.extended_volumes);
SwitchCompat s2 = findViewById(R.id.extended_volumes);
s2.setChecked(isExtendedVolumesEnabled());
s2.setOnCheckedChangeListener((buttonView, isChecked) -> setExtendedVolumesEnabled(isChecked));

Expand All @@ -235,11 +236,11 @@ private void buildUi() {
control.addOnRingerModeListener(ringerModeSwitcher);
ringerModeSwitch.setVisibility(View.GONE);

Switch notificationSwitch = findViewById(R.id.notification_widget);
SwitchCompat notificationSwitch = findViewById(R.id.notification_widget);

notificationSwitch.setChecked(isNotificationWidgetEnabled());

Switch profilesSwitch = findViewById(R.id.notification_widget_profiles);
SwitchCompat profilesSwitch = findViewById(R.id.notification_widget_profiles);

profilesSwitch.setChecked(settings.showProfilesInNotification);

Expand All @@ -265,7 +266,7 @@ private void buildUi() {
}
});

Switch vibrateOnCalls = findViewById(R.id.vibrate_on_calls);
SwitchCompat vibrateOnCalls = findViewById(R.id.vibrate_on_calls);
vibrateOnCalls.setOnCheckedChangeListener((compoundButton, isEnabled) -> {
if (checkWriteSettingsPermission(MainActivity.this, 0)) {
control.setVibrateOnCalls(isEnabled);
Expand All @@ -287,7 +288,7 @@ private void buildUi() {
}

private void updateVibrateOnCalls() {
Switch vibrateOnCalls = findViewById(R.id.vibrate_on_calls);
SwitchCompat vibrateOnCalls = findViewById(R.id.vibrate_on_calls);
try {
vibrateOnCalls.setChecked(control.isVibrateOnCallsEnabled());
} catch (Throwable throwable) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.example.punksta.volumecontrol;

import java.util.Locale;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
Expand All @@ -15,6 +19,7 @@

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;

import com.example.punksta.volumecontrol.data.Settings;
import com.example.punksta.volumecontrol.data.SoundProfile;
Expand Down Expand Up @@ -149,7 +154,7 @@ private static Notification buildForegroundNotification(

int requestId = PROFILE_ID_PREFIX + profile.id;

pendingIntent = PendingIntent.getService(context, requestId, i, 0);
pendingIntent = PendingIntent.getService(context, requestId, i, PendingIntent.FLAG_IMMUTABLE);
profileViews.setOnClickPendingIntent(R.id.notification_profile_title, pendingIntent);
remoteViews.addView(R.id.notifications_user_profiles, profileViews);
}
Expand All @@ -164,15 +169,19 @@ private static Notification buildForegroundNotification(
}
}

remoteViews.setOnClickPendingIntent(R.id.remove_notification_action, PendingIntent.getService(context, 100, getStopIntent(context), 0));
remoteViews.setOnClickPendingIntent(R.id.remove_notification_action,
PendingIntent.getService(context, 100,
getStopIntent(context), PendingIntent.FLAG_IMMUTABLE));
}
builder
.setContentTitle(context.getString(R.string.app_name))
.setOngoing(true)
.setContentText(context.getString(R.string.notification_widget))
.setSmallIcon(R.drawable.notification_icon)
.setTicker(context.getString(R.string.app_name))
.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0));
.setContentIntent(PendingIntent.getActivity(context, 0, new
Intent(context, MainActivity.class),
PendingIntent.FLAG_IMMUTABLE));


if ((volumeTypesToShow != null && volumeTypesToShow.size() > 0) || (profiles != null && profiles.length > 0)) {
Expand All @@ -184,7 +193,7 @@ private static Notification buildForegroundNotification(
}

private static String capitalize(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
return str.substring(0, 1).toUpperCase(Locale.getDefault()) + str.substring(1);
}

public static Intent getIntentForProfile(Context content, SoundProfile profile) {
Expand Down Expand Up @@ -254,8 +263,14 @@ private void updateNotification(boolean startService, boolean force) {
n
);
} else {
notificationManagerCompat.notify(staticNotificationNumber,
n);
boolean canNotify = (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) ? true // before Android13 there's no permission to ask
: (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED);

if( canNotify ){
notificationManagerCompat.notify(staticNotificationNumber, n);
}
// TODO: mention permission missing, or start request flow...
//else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {}
}

isForeground = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
Expand All @@ -22,6 +23,13 @@ private static String profileToShortcutId(SoundProfile profile) {
return "profile_" + profile.id.toString();
}

public static void reportUsage(Context context, SoundProfile profile){
if (Build.VERSION.SDK_INT >= 25) {
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
shortcutManager.reportShortcutUsed(profileToShortcutId(profile));
}
}

private static ShortcutInfo createShortcutInfo(Activity activity, SoundProfile profile) {
return new ShortcutInfo.Builder(activity.getApplicationContext(), profileToShortcutId(profile))
.setIntent(MainActivity.createOpenProfileIntent(activity, profile))
Expand Down
Loading