1 /** 2 * Copyright (C) 2017 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 17 package android.hardware.radio; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.util.Log; 24 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Objects; 28 29 /** 30 * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback. 31 */ 32 class TunerCallbackAdapter extends ITunerCallback.Stub { 33 private static final String TAG = "BroadcastRadio.TunerCallbackAdapter"; 34 35 private final Object mLock = new Object(); 36 @NonNull private final RadioTuner.Callback mCallback; 37 @NonNull private final Handler mHandler; 38 39 @Nullable ProgramList mProgramList; 40 41 // cache for deprecated methods 42 boolean mIsAntennaConnected = true; 43 @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; 44 private boolean mDelayedCompleteCallback = false; 45 @Nullable RadioManager.ProgramInfo mCurrentProgramInfo; 46 47 TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) { 48 mCallback = callback; 49 if (handler == null) { 50 mHandler = new Handler(Looper.getMainLooper()); 51 } else { 52 mHandler = handler; 53 } 54 } 55 56 void close() { 57 synchronized (mLock) { 58 if (mProgramList != null) mProgramList.close(); 59 } 60 } 61 62 void setProgramListObserver(@Nullable ProgramList programList, 63 @NonNull ProgramList.OnCloseListener closeListener) { 64 Objects.requireNonNull(closeListener); 65 synchronized (mLock) { 66 if (mProgramList != null) { 67 Log.w(TAG, "Previous program list observer wasn't properly closed, closing it..."); 68 mProgramList.close(); 69 } 70 mProgramList = programList; 71 if (programList == null) return; 72 programList.setOnCloseListener(() -> { 73 synchronized (mLock) { 74 if (mProgramList != programList) return; 75 mProgramList = null; 76 mLastCompleteList = null; 77 closeListener.onClose(); 78 } 79 }); 80 programList.addOnCompleteListener(() -> { 81 synchronized (mLock) { 82 if (mProgramList != programList) return; 83 mLastCompleteList = programList.toList(); 84 if (mDelayedCompleteCallback) { 85 Log.d(TAG, "Sending delayed onBackgroundScanComplete callback"); 86 sendBackgroundScanCompleteLocked(); 87 } 88 } 89 }); 90 } 91 } 92 93 @Nullable List<RadioManager.ProgramInfo> getLastCompleteList() { 94 synchronized (mLock) { 95 return mLastCompleteList; 96 } 97 } 98 99 void clearLastCompleteList() { 100 synchronized (mLock) { 101 mLastCompleteList = null; 102 } 103 } 104 105 @Nullable RadioManager.ProgramInfo getCurrentProgramInformation() { 106 synchronized (mLock) { 107 return mCurrentProgramInfo; 108 } 109 } 110 111 boolean isAntennaConnected() { 112 return mIsAntennaConnected; 113 } 114 115 @Override 116 public void onError(int status) { 117 mHandler.post(() -> mCallback.onError(status)); 118 } 119 120 @Override 121 public void onTuneFailed(int status, @Nullable ProgramSelector selector) { 122 mHandler.post(() -> mCallback.onTuneFailed(status, selector)); 123 124 int errorCode; 125 switch (status) { 126 case RadioManager.STATUS_PERMISSION_DENIED: 127 case RadioManager.STATUS_DEAD_OBJECT: 128 errorCode = RadioTuner.ERROR_SERVER_DIED; 129 break; 130 case RadioManager.STATUS_ERROR: 131 case RadioManager.STATUS_NO_INIT: 132 case RadioManager.STATUS_BAD_VALUE: 133 case RadioManager.STATUS_INVALID_OPERATION: 134 Log.i(TAG, "Got an error with no mapping to the legacy API (" + status 135 + "), doing a best-effort conversion to ERROR_SCAN_TIMEOUT"); 136 // fall through 137 case RadioManager.STATUS_TIMED_OUT: 138 default: 139 errorCode = RadioTuner.ERROR_SCAN_TIMEOUT; 140 } 141 mHandler.post(() -> mCallback.onError(errorCode)); 142 } 143 144 @Override 145 public void onConfigurationChanged(RadioManager.BandConfig config) { 146 mHandler.post(() -> mCallback.onConfigurationChanged(config)); 147 } 148 149 @Override 150 public void onCurrentProgramInfoChanged(RadioManager.ProgramInfo info) { 151 if (info == null) { 152 Log.e(TAG, "ProgramInfo must not be null"); 153 return; 154 } 155 156 synchronized (mLock) { 157 mCurrentProgramInfo = info; 158 } 159 160 mHandler.post(() -> { 161 mCallback.onProgramInfoChanged(info); 162 163 RadioMetadata metadata = info.getMetadata(); 164 if (metadata != null) mCallback.onMetadataChanged(metadata); 165 }); 166 } 167 168 @Override 169 public void onTrafficAnnouncement(boolean active) { 170 mHandler.post(() -> mCallback.onTrafficAnnouncement(active)); 171 } 172 173 @Override 174 public void onEmergencyAnnouncement(boolean active) { 175 mHandler.post(() -> mCallback.onEmergencyAnnouncement(active)); 176 } 177 178 @Override 179 public void onAntennaState(boolean connected) { 180 mIsAntennaConnected = connected; 181 mHandler.post(() -> mCallback.onAntennaState(connected)); 182 } 183 184 @Override 185 public void onBackgroundScanAvailabilityChange(boolean isAvailable) { 186 mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable)); 187 } 188 189 private void sendBackgroundScanCompleteLocked() { 190 mDelayedCompleteCallback = false; 191 mHandler.post(() -> mCallback.onBackgroundScanComplete()); 192 } 193 194 @Override 195 public void onBackgroundScanComplete() { 196 synchronized (mLock) { 197 if (mLastCompleteList == null) { 198 Log.i(TAG, "Got onBackgroundScanComplete callback, but the " 199 + "program list didn't get through yet. Delaying it..."); 200 mDelayedCompleteCallback = true; 201 return; 202 } 203 sendBackgroundScanCompleteLocked(); 204 } 205 } 206 207 @Override 208 public void onProgramListChanged() { 209 mHandler.post(() -> mCallback.onProgramListChanged()); 210 } 211 212 @Override 213 public void onProgramListUpdated(ProgramList.Chunk chunk) { 214 synchronized (mLock) { 215 if (mProgramList == null) return; 216 mProgramList.apply(Objects.requireNonNull(chunk)); 217 } 218 } 219 220 @Override 221 public void onParametersUpdated(Map parameters) { 222 mHandler.post(() -> mCallback.onParametersUpdated(parameters)); 223 } 224 } 225