[JAVA] Créez une application Android pour ceux qui ne veulent pas écouter de musique sur leurs haut-parleurs

Dans le train - dans une bibliothèque tranquille - dans une salle de conférence - Lorsque j'ai lancé l'application, j'ai entendu un son fort. Avez-vous une telle expérience? Heureusement, je ne l'ai pas encore expérimenté, mais j'ai l'habitude de vérifier le volume plusieurs fois.

J'ai créé une application pour résoudre un tel problème.

DoNotSpeak: une application qui ne lit pas de musique sur le haut-parleur

Téléchargez à partir du lien ci-dessous Get it on Google Play

Il est open source (licence MIT). Dépôt

Que faire

Cette application règle le volume du haut-parleur (STREAM_MUSIC) à zéro lorsque l'écouteur n'est pas connecté. Réglez le volume sur zéro au moment suivant.

Fonction de libération temporaire

Cette application réside dans la zone de notification (service de premier plan) et essaie toujours de réduire le volume du haut-parleur à zéro. Cependant, il y a des moments où vous souhaitez utiliser un haut-parleur, nous avons donc implémenté une fonction pour annuler temporairement le processus de mise en sourdine.

Appuyez sur Ne pas parler! Dans la notification pour afficher la boîte de dialogue d'annulation.

Détails techniques

Si seulement ce qui précède est fait, ce sera un article publicitaire, donc j'écrirai le contenu de la mise en œuvre.

Réglage du volume

C'est le cœur de cette application, qui réduit le volume du haut-parleur à zéro. Il peut être défini dans la classe AudioManager.

AudioManager audioManager = this.getSystemService(this, AudioManager.class);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);

Récupérez simplement le ʻAudioManager et appelez la méthode setStreamVolume. Sur Android, les paramètres de volume sont séparés pour chaque flux et chaque volume peut être réglé indépendamment. Spécifiez STREAM_MUSIC` pour ne mettre à zéro que la musique de cette application.

De plus, il semble que l'autorisation ʻandroid.permission.MODIFY_AUDIO_SETTINGS` soit nécessaire, mais ce n'était pas le cas.

État de la connexion des écouteurs

Cette application ne règle pas le volume à zéro lorsque les écouteurs sont connectés, vous devez donc vérifier l'état de connexion des écouteurs. Vous pouvez vérifier l'état de la connexion avec ʻAudioManager, mais le code de jugement change en fonction de la version d'Android. Android avant Android 6.0 M utilise les méthodes ʻisWiredHeadsetOn, ʻisBluetoothScoOn et ʻisBluetoothA2dpOn, et Android 6.0 M utilise la méthode getDevices.

private boolean isHeadsetConnected() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        return this.audioManager.isWiredHeadsetOn() || this.audioManager.isBluetoothScoOn() || this.audioManager.isBluetoothA2dpOn();
    } else {
        AudioDeviceInfo[] devices = this.audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
        for (int i = 0; i < devices.length; i++) {
            AudioDeviceInfo device = devices[i];

            int type = device.getType();
            if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET
                    || type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
                    || type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
                    || type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
            ) {
                return true;
            }
        }

        return false;
    }
}

Bluetooth A2DP semble être utilisé dans les écouteurs Bluetooth courants, mais Bluetooth SCO peut ne pas être nécessaire.

Détection de changement de volume

Même si le volume est réglé sur zéro, cela n'a aucun sens si l'utilisateur ou d'autres applications modifient le volume. Par conséquent, nous avons besoin d'une fonction pour rendre le volume nul lorsque le volume est modifié. Sur Android, Content Provider et ContentObserver Vous pouvez détecter le changement de volume en vous enregistrant.

public final class DNSContentObserver extends ContentObserver {
    private Runnable callback;

    public DNSContentObserver(Handler handler, Runnable callback) {
        super(handler);
        this.callback = callback;
    }

    @Override
    public boolean deliverSelfNotifications() {
        return false;
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        this.callback.run();
    }
}

this.getContentResolver().registerContentObserver(
  android.provider.Settings.System.getUriFor("volume_music_speaker"),
  true,
  new DNSContentObserver(new Handler(), new Runnable() {
      @Override
      public void run() {
          //Traitement muet
      }
  }));

Récupérez la classe ContentResolver avec getContentResolver et enregistrez ContentObserver avec la méthode registerContentObserver. À ce stade, vous pouvez spécifier les données à surveiller à l'aide de la chaîne de caractères appelée URI de contenu. Dans cette application, nous voulons détecter les changements de volume du haut-parleur, nous spécifions donc content: // settings / system / volume_music_speaker.

Traitement anti-rebond

Dans le processus de détection de changement de volume ci-dessus, si le volume est changé continuellement en peu de temps, ʻonChangedeContentObserver peut ne pas se produire (plusieurs changements peuvent être combinés en un ʻonChange?). Pour atténuer cet effet, coupez à nouveau le son 1 seconde après que ʻonChange` se soit produit. J'ai créé une classe pour faire le traitement debounce pour cela.

public final class Debouncer {
    private static final String TAG = "Debouncer";

    private final Handler handler;
    private final int dueTime;
    private final Runnable callback;
    private final Runnable checkRunner = new Runnable() {
        @Override
        public void run() {
            Debouncer.this.check();
        }
    };

    private final AtomicBoolean locked = new AtomicBoolean(false);
    private int elapsedTime;
    private long startTime;

    public Debouncer(Handler handler, int dueTime, Runnable callback) {
        this.handler = handler;
        this.dueTime = dueTime;
        this.callback = callback;
        this.elapsedTime = this.dueTime;
    }

    public void update() {
        for (; ; ) {
            // lock
            if (this.locked.compareAndSet(false, true)) {
                int elapsed = this.elapsedTime;

                // reset time
                Log.d(TAG, "reset");
                this.startTime = System.currentTimeMillis();
                this.elapsedTime = 0;

                // not running?
                if (elapsed >= this.dueTime) {
                    this.start(this.dueTime);
                }

                // unlock
                this.locked.set(false);
                break;
            }
        }
    }

    private void start(int delayTime) {
        Log.d(TAG, "start");
        this.handler.postDelayed(this.checkRunner, delayTime);
    }

    private void check() {
        Log.d(TAG, "check");
        // lock
        if (this.locked.compareAndSet(false, true)) {
            long currentTime = System.currentTimeMillis();
            this.elapsedTime += currentTime - this.startTime;
            boolean over = this.elapsedTime >= this.dueTime;

            // retry
            if (!over) {
                int remainTime = this.dueTime - this.elapsedTime;
                this.startTime = currentTime;
                this.start(remainTime);
            }

            // unlock
            this.locked.set(false);

            // callback
            if (over) this.callback.run();
        }
    }
}

Détecte que l'écouteur a été retiré

Sur Android, ʻandroid.media.AUDIO_BECOMING_NOISY` est diffusé au moment où l'écouteur est retiré et le son est émis par le haut-parleur.

public final class DNSReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null) return;

        switch (action) {
            case AudioManager.ACTION_AUDIO_BECOMING_NOISY: {
                //Traitement muet
                break;
            }
        }
    }
}

J'écris également un filtre d'intention dans le manifeste.

<intent-filter>
    <action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>

À propos, avant de mettre à jour l'état de connexion de l'écouteur dans Android, cette diffusion est appelée, vous ne devez donc pas vérifier si l'écouteur est connecté à ce moment. Cette application sera désactivée de force quel que soit l'état de la connexion des écouteurs.

redémarrer

Le service sera interrompu lors du redémarrage du terminal ou de la mise à jour de l'application. Si rien n'est fait, une tragédie peut survenir, il est donc nécessaire de démarrer l'application automatiquement. Dans un tel cas, recevez la diffusion ʻandroid.intent.action.BOOT_COMPLETED et la diffusion ʻandroid.intent.action.MY_PACKAGE_REPLACED et démarrez le service.

<intent-filter>
    <action android:name="android.intent.action.BOOT_COMPLETED" />
    <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>

Vous avez également besoin d'autorisations.

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

Le code est similaire à ʻAUDIO_BECOMING_NOISY`, donc il est omis.

Lancer le service à partir du lanceur

Sur Android, vous ne pouvez pas lancer de services directement depuis le lanceur. Vous ne pouvez (probablement) lancer des activités qu'à partir du lanceur. Cependant, cette application n'a pas besoin d'afficher l'activité, je l'ai donc ajustée pour ne pas afficher d'activité.

Remplacez ʻAppTheme` par:

<resources>
    <style name="AppTheme" parent="android:Theme.Material">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
    </style>
</resources>

Ce sera un thème transparent pour les activités sans barre de titre. Vous pouvez alors démarrer le service avec ʻonCreate dans MainActivityet appeler immédiatementfinishAndRemoveTask` pour masquer l'activité de l'apparence.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //Démarrer le service
    this.finishAndRemoveTask();
    this.overridePendingTransition(0, 0);
}

De plus, il semble que l'animation de transition puisse être annulée en appelant ʻoverridePendingTransition`.

Réduire la taille de l'application

Lorsque j'ai créé une application dans Android Studio, la taille de l'APK dépassait 1 Mo. Pour les applications résidentes, il est certainement préférable d'avoir une utilisation de la mémoire plus petite, nous avons donc effectué diverses réductions pour réduire la taille de l'application.

Tout d'abord, définissez minifyEnabled et shirinkResources sur true dans ProGuard. Juste en faisant cela, cela faisait environ 700 Ko.

release {
    minifyEnabled true
    shrinkResources true
    ...
}

Normalement, je ne pense pas qu'il y ait de problème s'il est aussi petit que le niveau KB, mais je n'étais pas satisfait et j'ai décidé de le réduire. Dans cette application, la bibliothèque de dépendances spécifiée par dependencies a gonflé l'APK.

build.gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
    implementation 'androidx.core:core-ktx:1.1.0-alpha03'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
}

Il s'agit d'Android X, une bibliothèque de support pour maintenir la compatibilité descendante avec Android, et je pense que c'est presque essentiel pour les applications générales. Cependant, vous pouvez réduire la taille de l'APK en les jetant. (Non recommandé.)

J'ai pu créer un APK de moins de 100 Ko en supprimant AndroidX et en écrivant mon propre code rétrocompatible. La taille finale est d'environ 26 Ko. (Au fait, je l'ai écrit dans Kotlin au début, mais je l'ai changé en Java car le code d'octet généré était subtil. Objet compagnon ...)

Recommended Posts

Créez une application Android pour ceux qui ne veulent pas écouter de musique sur leurs haut-parleurs
L'histoire de la sortie de l'application Android sur le Play Store pour la première fois.
[Pour ceux qui créent des portfolios] Comment utiliser font-awesome-rails
Je souhaite simplifier la sortie du journal sur Android
Pour ceux qui souhaitent utiliser MySQL pour la base de données dans la construction de l'environnement de Rails6 ~.
FAQ sur VS Code pour ceux qui veulent échapper à Eclipse
[Pour ceux qui créent des portefeuilles] Comment utiliser Chart kick
[Pour ceux qui créent des portfolios] Comment omettre les chaînes de caractères
[Pour ceux qui veulent être un ingénieur littéraire inexpérimenté] Aimeriez-vous affronter les mathématiques échappées à travers Project_Euler?
[Pour ceux qui créent des portefeuilles] Réduisez les erreurs et facilitez la lecture du code -Rubocop Airbnb-
[Pour ceux qui créent des portefeuilles] Comment utiliser binding.pry avec Docker
Créer une application et la déployer pour la première fois avec heroku
Je veux créer une application ios.android
[Android] Je souhaite créer un ViewPager pouvant être utilisé pour les didacticiels
Premiers pas avec Java
J'ai créé une application Android pour le service MiRm
Connexion SSH au serveur d'applications avec heroku
Créez votre propre application Android pour l'apprentissage Java
Créer une application en spécifiant la version de Rails
Comment créer un lecteur de musique Android imposant
[Android Studio] Je souhaite définir des restrictions sur les valeurs enregistrées dans EditText [Java]
Créez une application qui utilise l'API météo pour déterminer si vous avez besoin d'un parapluie.