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