1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tv.receiver;
17 
18 import android.content.BroadcastReceiver;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.SharedPreferences;
23 import android.media.AudioFormat;
24 import android.media.AudioManager;
25 import android.support.annotation.NonNull;
26 import android.support.annotation.Nullable;
27 import com.android.tv.TvSingletons;
28 import com.android.tv.analytics.Analytics;
29 import com.android.tv.analytics.Tracker;
30 import com.android.tv.common.util.SharedPreferencesUtils;
31 
32 /**
33  * Creates HDMI plug broadcast receiver, and reports AC3 passthrough capabilities to Google
34  * Analytics and listeners. Call {@link #register} to start receiving notifications, and {@link
35  * #unregister} to stop.
36  */
37 public final class AudioCapabilitiesReceiver {
38     private static final String SETTINGS_KEY_AC3_PASSTHRU_REPORTED = "ac3_passthrough_reported";
39     private static final String SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES = "ac3_passthrough";
40     private static final String SETTINGS_KEY_AC3_REPORT_REVISION = "ac3_report_revision";
41 
42     // AC3 capabilities stat is sent to Google Analytics just once in order to avoid
43     // duplicated stat reports since it doesn't change over time in most cases.
44     // Increase this revision when we should force the stat to be sent again.
45     // TODO: Consider using custom metrics.
46     private static final int REPORT_REVISION = 1;
47 
48     private final Context mContext;
49     private final Analytics mAnalytics;
50     private final Tracker mTracker;
51     @Nullable private final OnAc3PassthroughCapabilityChangeListener mListener;
52     private final BroadcastReceiver mReceiver = new HdmiAudioPlugBroadcastReceiver();
53 
54     /**
55      * Constructs a new audio capabilities receiver.
56      *
57      * @param context context for registering to receive broadcasts
58      * @param listener listener which receives AC3 passthrough capability change notification
59      */
AudioCapabilitiesReceiver( @onNull Context context, @Nullable OnAc3PassthroughCapabilityChangeListener listener)60     public AudioCapabilitiesReceiver(
61             @NonNull Context context, @Nullable OnAc3PassthroughCapabilityChangeListener listener) {
62         mContext = context;
63         TvSingletons tvSingletons = TvSingletons.getSingletons(context);
64         mAnalytics = tvSingletons.getAnalytics();
65         mTracker = tvSingletons.getTracker();
66         mListener = listener;
67     }
68 
register()69     public void register() {
70         mContext.registerReceiver(mReceiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
71     }
72 
unregister()73     public void unregister() {
74         mContext.unregisterReceiver(mReceiver);
75     }
76 
77     private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver {
78         @Override
onReceive(Context context, Intent intent)79         public void onReceive(Context context, Intent intent) {
80             String action = intent.getAction();
81             if (!action.equals(AudioManager.ACTION_HDMI_AUDIO_PLUG)) {
82                 return;
83             }
84             boolean supported = false;
85             int[] supportedEncodings = intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS);
86             if (supportedEncodings != null) {
87                 for (int supportedEncoding : supportedEncodings) {
88                     if (supportedEncoding == AudioFormat.ENCODING_AC3) {
89                         supported = true;
90                         break;
91                     }
92                 }
93             }
94             if (mListener != null) {
95                 mListener.onAc3PassthroughCapabilityChange(supported);
96             }
97             if (!mAnalytics.isAppOptOut()) {
98                 reportAudioCapabilities(supported);
99             }
100         }
101     }
102 
reportAudioCapabilities(boolean ac3Supported)103     private void reportAudioCapabilities(boolean ac3Supported) {
104         boolean oldVal = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, false);
105         boolean reported = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, false);
106         int revision = getInt(SETTINGS_KEY_AC3_REPORT_REVISION, 0);
107 
108         // Send the value just once. But we send it again if the value changed, to include
109         // the case where users have switched TV device with different AC3 passthrough capabilities.
110         if (!reported || oldVal != ac3Supported || REPORT_REVISION > revision) {
111             mTracker.sendAc3PassthroughCapabilities(ac3Supported);
112             setBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, true);
113             setBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, ac3Supported);
114             if (REPORT_REVISION > revision) {
115                 setInt(SETTINGS_KEY_AC3_REPORT_REVISION, REPORT_REVISION);
116             }
117         }
118     }
119 
getSharedPreferences()120     private SharedPreferences getSharedPreferences() {
121         return mContext.getSharedPreferences(
122                 SharedPreferencesUtils.SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE);
123     }
124 
getBoolean(String key, boolean def)125     private boolean getBoolean(String key, boolean def) {
126         return getSharedPreferences().getBoolean(key, def);
127     }
128 
setBoolean(String key, boolean val)129     private void setBoolean(String key, boolean val) {
130         getSharedPreferences().edit().putBoolean(key, val).apply();
131     }
132 
getInt(String key, int def)133     private int getInt(String key, int def) {
134         return getSharedPreferences().getInt(key, def);
135     }
136 
setInt(String key, int val)137     private void setInt(String key, int val) {
138         getSharedPreferences().edit().putInt(key, val).apply();
139     }
140 
141     /** Listener notified when AC3 passthrough capability changes. */
142     public interface OnAc3PassthroughCapabilityChangeListener {
143         /** Called when the AC3 passthrough capability changes. */
onAc3PassthroughCapabilityChange(boolean capability)144         void onAc3PassthroughCapabilityChange(boolean capability);
145     }
146 }
147