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                                   Context.RECEIVER_EXPORTED);
72     }
73 
unregister()74     public void unregister() {
75         mContext.unregisterReceiver(mReceiver);
76     }
77 
78     private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver {
79         @Override
onReceive(Context context, Intent intent)80         public void onReceive(Context context, Intent intent) {
81             String action = intent.getAction();
82             if (!action.equals(AudioManager.ACTION_HDMI_AUDIO_PLUG)) {
83                 return;
84             }
85             boolean supported = false;
86             int[] supportedEncodings = intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS);
87             if (supportedEncodings != null) {
88                 for (int supportedEncoding : supportedEncodings) {
89                     if (supportedEncoding == AudioFormat.ENCODING_AC3) {
90                         supported = true;
91                         break;
92                     }
93                 }
94             }
95             if (mListener != null) {
96                 mListener.onAc3PassthroughCapabilityChange(supported);
97             }
98             if (!mAnalytics.isAppOptOut()) {
99                 reportAudioCapabilities(supported);
100             }
101         }
102     }
103 
reportAudioCapabilities(boolean ac3Supported)104     private void reportAudioCapabilities(boolean ac3Supported) {
105         boolean oldVal = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, false);
106         boolean reported = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, false);
107         int revision = getInt(SETTINGS_KEY_AC3_REPORT_REVISION, 0);
108 
109         // Send the value just once. But we send it again if the value changed, to include
110         // the case where users have switched TV device with different AC3 passthrough capabilities.
111         if (!reported || oldVal != ac3Supported || REPORT_REVISION > revision) {
112             mTracker.sendAc3PassthroughCapabilities(ac3Supported);
113             setBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, true);
114             setBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, ac3Supported);
115             if (REPORT_REVISION > revision) {
116                 setInt(SETTINGS_KEY_AC3_REPORT_REVISION, REPORT_REVISION);
117             }
118         }
119     }
120 
getSharedPreferences()121     private SharedPreferences getSharedPreferences() {
122         return mContext.getSharedPreferences(
123                 SharedPreferencesUtils.SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE);
124     }
125 
getBoolean(String key, boolean def)126     private boolean getBoolean(String key, boolean def) {
127         return getSharedPreferences().getBoolean(key, def);
128     }
129 
setBoolean(String key, boolean val)130     private void setBoolean(String key, boolean val) {
131         getSharedPreferences().edit().putBoolean(key, val).apply();
132     }
133 
getInt(String key, int def)134     private int getInt(String key, int def) {
135         return getSharedPreferences().getInt(key, def);
136     }
137 
setInt(String key, int val)138     private void setInt(String key, int val) {
139         getSharedPreferences().edit().putInt(key, val).apply();
140     }
141 
142     /** Listener notified when AC3 passthrough capability changes. */
143     public interface OnAc3PassthroughCapabilityChangeListener {
144         /** Called when the AC3 passthrough capability changes. */
onAc3PassthroughCapabilityChange(boolean capability)145         void onAc3PassthroughCapabilityChange(boolean capability);
146     }
147 }
148