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 
28 import com.android.tv.ApplicationSingletons;
29 import com.android.tv.TvApplication;
30 import com.android.tv.analytics.Analytics;
31 import com.android.tv.analytics.Tracker;
32 import com.android.tv.common.SharedPreferencesUtils;
33 
34 /**
35  * Creates HDMI plug broadcast receiver, and reports AC3 passthrough capabilities to Google
36  * Analytics and listeners. Call {@link #register} to start receiving notifications, and
37  * {@link #unregister} to stop.
38  */
39 public final class AudioCapabilitiesReceiver {
40     private static final String SETTINGS_KEY_AC3_PASSTHRU_REPORTED = "ac3_passthrough_reported";
41     private static final String SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES = "ac3_passthrough";
42     private static final String SETTINGS_KEY_AC3_REPORT_REVISION = "ac3_report_revision";
43 
44     // AC3 capabilities stat is sent to Google Analytics just once in order to avoid
45     // duplicated stat reports since it doesn't change over time in most cases.
46     // Increase this revision when we should force the stat to be sent again.
47     // TODO: Consier using custom metrics.
48     private static final int REPORT_REVISION = 1;
49 
50     private final Context mContext;
51     private final Analytics mAnalytics;
52     private final Tracker mTracker;
53     @Nullable
54     private final OnAc3PassthroughCapabilityChangeListener mListener;
55     private final BroadcastReceiver mReceiver = new HdmiAudioPlugBroadcastReceiver();
56 
57     /**
58      * Constructs a new audio capabilities receiver.
59      *
60      * @param context context for registering to receive broadcasts
61      * @param listener listener which receives AC3 passthrough capability change notification
62      */
AudioCapabilitiesReceiver(@onNull Context context, @Nullable OnAc3PassthroughCapabilityChangeListener listener)63     public AudioCapabilitiesReceiver(@NonNull Context context,
64             @Nullable OnAc3PassthroughCapabilityChangeListener listener) {
65         mContext = context;
66         ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
67         mAnalytics = appSingletons.getAnalytics();
68         mTracker = appSingletons.getTracker();
69         mListener = listener;
70     }
71 
register()72     public void register() {
73         mContext.registerReceiver(mReceiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
74     }
75 
unregister()76     public void unregister() {
77         mContext.unregisterReceiver(mReceiver);
78     }
79 
80     private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver {
81         @Override
onReceive(Context context, Intent intent)82         public void onReceive(Context context, Intent intent) {
83             String action = intent.getAction();
84             if (!action.equals(AudioManager.ACTION_HDMI_AUDIO_PLUG)) {
85                 return;
86             }
87             boolean supported = false;
88             int[] supportedEncodings = intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS);
89             if (supportedEncodings != null) {
90                 for (int supportedEncoding : supportedEncodings) {
91                     if (supportedEncoding == AudioFormat.ENCODING_AC3) {
92                         supported = true;
93                         break;
94                     }
95                 }
96             }
97             if (mListener != null) {
98                 mListener.onAc3PassthroughCapabilityChange(supported);
99             }
100             if (!mAnalytics.isAppOptOut()) {
101                 reportAudioCapabilities(supported);
102             }
103         }
104     }
105 
reportAudioCapabilities(boolean ac3Supported)106     private void reportAudioCapabilities(boolean ac3Supported) {
107         boolean oldVal = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, false);
108         boolean reported = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, false);
109         int revision = getInt(SETTINGS_KEY_AC3_REPORT_REVISION, 0);
110 
111         // Send the value just once. But we send it again if the value changed, to include
112         // the case where users have switched TV device with different AC3 passthrough capabilities.
113         if (!reported || oldVal != ac3Supported || REPORT_REVISION > revision) {
114             mTracker.sendAc3PassthroughCapabilities(ac3Supported);
115             setBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, true);
116             setBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, ac3Supported);
117             if (REPORT_REVISION > revision) {
118                 setInt(SETTINGS_KEY_AC3_REPORT_REVISION, REPORT_REVISION);
119             }
120         }
121     }
122 
getSharedPreferences()123     private SharedPreferences getSharedPreferences() {
124         return mContext.getSharedPreferences(SharedPreferencesUtils.SHARED_PREF_AUDIO_CAPABILITIES,
125                 Context.MODE_PRIVATE);
126     }
127 
getBoolean(String key, boolean def)128     private boolean getBoolean(String key, boolean def) {
129         return getSharedPreferences().getBoolean(key, def);
130     }
131 
setBoolean(String key, boolean val)132     private void setBoolean(String key, boolean val) {
133         getSharedPreferences().edit().putBoolean(key, val).apply();
134     }
135 
getInt(String key, int def)136     private int getInt(String key, int def) {
137         return getSharedPreferences().getInt(key, def);
138     }
139 
setInt(String key, int val)140     private void setInt(String key, int val) {
141         getSharedPreferences().edit().putInt(key, val).apply();
142     }
143 
144     /**
145      * Listener notified when AC3 passthrough capability changes.
146      */
147     public interface OnAc3PassthroughCapabilityChangeListener {
148         /**
149          * Called when the AC3 passthrough capability changes.
150          */
onAc3PassthroughCapabilityChange(boolean capability)151         void onAc3PassthroughCapabilityChange(boolean capability);
152     }
153 }
154