163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo/* 263a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * Copyright (C) 2014 The Android Open Source Project 363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * 463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * Licensed under the Apache License, Version 2.0 (the "License"); 563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * you may not use this file except in compliance with the License. 663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * You may obtain a copy of the License at 763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * 863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * http://www.apache.org/licenses/LICENSE-2.0 963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * 1063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * Unless required by applicable law or agreed to in writing, software 1163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * distributed under the License is distributed on an "AS IS" BASIS, 1263a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * See the License for the specific language governing permissions and 1463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * limitations under the License. 1563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo */ 1663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 1763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heopackage com.android.server.hdmi; 1863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 19ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jangimport android.annotation.Nullable; 2061f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jangimport android.hardware.hdmi.HdmiDeviceInfo; 21c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kimimport android.hardware.hdmi.HdmiControlManager; 22ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jangimport android.hardware.hdmi.IHdmiControlCallback; 23ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jangimport android.os.RemoteException; 24ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jangimport android.util.Slog; 2563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 26c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heoimport java.util.List; 27c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo 2863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo/** 2963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr. 3063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo */ 31b509c2ecd99619248b7a07fb0fa978bb27f25cc3Jungshik Jangabstract class SystemAudioAction extends HdmiCecFeatureAction { 3263a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo private static final String TAG = "SystemAudioAction"; 3363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 34c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo // Transient state to differentiate with STATE_NONE where the on-finished callback 35c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo // will not be called. 36c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo private static final int STATE_CHECK_ROUTING_IN_PRGRESS = 1; 37c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo 3863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo // State in which waits for <SetSystemAudioMode>. 39c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 2; 4063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 4163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo private static final int MAX_SEND_RETRY_COUNT = 2; 4263a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 4363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo private static final int ON_TIMEOUT_MS = 5000; 445fba96df30b6b50b3cb9fe1d783320b1cc3bd6eaJinsuk Kim private static final int OFF_TIMEOUT_MS = HdmiConfig.TIMEOUT_MS; 4563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 4663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo // Logical address of AV Receiver. 4763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo protected final int mAvrLogicalAddress; 4863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 4963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo // The target audio status of the action, whether to enable the system audio mode or not. 5063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo protected boolean mTargetAudioStatus; 5163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 52ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang @Nullable private final IHdmiControlCallback mCallback; 53ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang 5463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo private int mSendRetryCount = 0; 5563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 5663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo /** 5763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * Constructor 5863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * 5979c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang * @param source {@link HdmiCecLocalDevice} instance 6063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * @param avrAddress logical address of AVR device 6163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * @param targetStatus Whether to enable the system audio mode or not 62ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang * @param callback callback interface to be notified when it's done 6363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid 6463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo */ 65ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus, 66ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang IHdmiControlCallback callback) { 6779c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang super(source); 6861f4fbd2e8436a1ecd478c2a1f516d064a24d43bJungshik Jang HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 6963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo mAvrLogicalAddress = avrAddress; 7063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo mTargetAudioStatus = targetStatus; 71ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang mCallback = callback; 7263a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 7363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 74c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo // Seq #27 7563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo protected void sendSystemAudioModeRequest() { 76c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo List<RoutingControlAction> routingActions = getActions(RoutingControlAction.class); 77c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo if (!routingActions.isEmpty()) { 78a7221ce87683fab16603290378408ce92f02e88aJungshik Jang mState = STATE_CHECK_ROUTING_IN_PRGRESS; 79c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo // Should have only one Routing Control Action 80c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo RoutingControlAction routingAction = routingActions.get(0); 81c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo routingAction.addOnFinishedCallback(this, new Runnable() { 82c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo @Override 83c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo public void run() { 84c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo sendSystemAudioModeRequestInternal(); 85c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo } 86c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo }); 87c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo return; 88c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo } 89c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo sendSystemAudioModeRequestInternal(); 90c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo } 91c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo 92c516d65fd96cdc39f9935ddb80d26ee6499a77bfYuncheol Heo private void sendSystemAudioModeRequestInternal() { 9379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest( 9479c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang getSourceAddress(), 95e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim mAvrLogicalAddress, getSystemAudioModeRequestParam(), mTargetAudioStatus); 9663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo sendCommand(command, new HdmiControlService.SendMessageCallback() { 9763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo @Override 9863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo public void onSendCompleted(int error) { 99a7221ce87683fab16603290378408ce92f02e88aJungshik Jang if (error != Constants.SEND_RESULT_SUCCESS) { 1002e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang HdmiLogger.debug("Failed to send <System Audio Mode Request>:" + error); 10163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo setSystemAudioMode(false); 102d05f67f9721e1f9194a1f57cf7481b4be65366b3Yuncheol Heo finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); 10363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 10463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 10563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo }); 106a7221ce87683fab16603290378408ce92f02e88aJungshik Jang mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE; 107a7221ce87683fab16603290378408ce92f02e88aJungshik Jang addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS); 10863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 10963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 110e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim private int getSystemAudioModeRequestParam() { 111e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim // <System Audio Mode Request> takes the physical address of the source device 112e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim // as a parameter. Get it from following candidates, in the order listed below: 113e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim // 1) physical address of the active source 114e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim // 2) active routing path 115e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim // 3) physical address of TV 116e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim if (tv().getActiveSource().isValid()) { 117e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim return tv().getActiveSource().physicalAddress; 118e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim } 119e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim int param = tv().getActivePath(); 120e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim return param != Constants.INVALID_PHYSICAL_ADDRESS 121e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim ? param : Constants.PATH_INTERNAL; 122e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim } 123e65360216890886b2fa507ddb656820c166bba22Jinsuk Kim 12463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo private void handleSendSystemAudioModeRequestTimeout() { 12563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo if (!mTargetAudioStatus // Don't retry for Off case. 12663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) { 1272e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang HdmiLogger.debug("[T]:wait for <Set System Audio Mode>."); 12863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo setSystemAudioMode(false); 129c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); 13063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo return; 13163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 13263a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo sendSystemAudioModeRequest(); 13363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 13463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 13563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo protected void setSystemAudioMode(boolean mode) { 1367ecfbaed6e902aea151bc1919cf7771bbd868fc4Jinsuk Kim tv().setSystemAudioMode(mode, true); 13763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 13863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 13963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo @Override 14063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo final boolean processCommand(HdmiCecMessage cmd) { 1415352081c662299b618335bf3024058fa04ef2dfdJungshik Jang if (cmd.getSource() != mAvrLogicalAddress) { 1425352081c662299b618335bf3024058fa04ef2dfdJungshik Jang return false; 1435352081c662299b618335bf3024058fa04ef2dfdJungshik Jang } 14463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo switch (mState) { 14563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE: 146d05f67f9721e1f9194a1f57cf7481b4be65366b3Yuncheol Heo if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT 147339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang && (cmd.getParams()[0] & 0xFF) 148339227da7cf025ce4ae0c85ddc52643d63972321Jungshik Jang == Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST) { 1492e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang HdmiLogger.debug("Failed to start system audio mode request."); 150d05f67f9721e1f9194a1f57cf7481b4be65366b3Yuncheol Heo setSystemAudioMode(false); 151d05f67f9721e1f9194a1f57cf7481b4be65366b3Yuncheol Heo finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); 152d05f67f9721e1f9194a1f57cf7481b4be65366b3Yuncheol Heo return true; 153d05f67f9721e1f9194a1f57cf7481b4be65366b3Yuncheol Heo } 154c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim if (cmd.getOpcode() != Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE 15563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) { 15663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo return false; 15763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 15863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd); 15963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo if (receivedStatus == mTargetAudioStatus) { 16063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo setSystemAudioMode(receivedStatus); 16116321b80077815eebeceee249ff7a29e336e04a4Jungshik Jang startAudioStatusAction(); 16216321b80077815eebeceee249ff7a29e336e04a4Jungshik Jang return true; 16363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } else { 1642e8f1b6399089626b4f0249427626ba6e63a62efJungshik Jang HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus); 16563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo // Unexpected response, consider the request is newly initiated by AVR. 16663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo // To return 'false' will initiate new SystemAudioActionFromAvr by the control 16763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo // service. 168c0c20d0522d7756d80f011e7a54bf3b51c78df41Jinsuk Kim finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); 16963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo return false; 17063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 17116321b80077815eebeceee249ff7a29e336e04a4Jungshik Jang default: 17216321b80077815eebeceee249ff7a29e336e04a4Jungshik Jang return false; 17363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 17416321b80077815eebeceee249ff7a29e336e04a4Jungshik Jang } 17516321b80077815eebeceee249ff7a29e336e04a4Jungshik Jang 17616321b80077815eebeceee249ff7a29e336e04a4Jungshik Jang protected void startAudioStatusAction() { 177ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallback)); 17816321b80077815eebeceee249ff7a29e336e04a4Jungshik Jang finish(); 17963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 18063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 18163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo protected void removeSystemAudioActionInProgress() { 18279c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang removeActionExcept(SystemAudioActionFromTv.class, this); 18379c58a4b97f27ede6a1b680d2fece9c2a0edf7b7Jungshik Jang removeActionExcept(SystemAudioActionFromAvr.class, this); 18463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 18563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo 18663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo @Override 18763a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo final void handleTimerEvent(int state) { 18863a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo if (mState != state) { 18963a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo return; 19063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 19163a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo switch (mState) { 19263a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE: 19363a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo handleSendSystemAudioModeRequestTimeout(); 19463a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo return; 19563a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 19663a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo } 197ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang 198ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang // TODO: if IHdmiControlCallback is general to other FeatureAction, 199ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang // move it into FeatureAction. 200ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang protected void finishWithCallback(int returnCode) { 201ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang if (mCallback != null) { 202ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang try { 203ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang mCallback.onComplete(returnCode); 204ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang } catch (RemoteException e) { 205ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang Slog.e(TAG, "Failed to invoke callback.", e); 206ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang } 207ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang } 208ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang finish(); 209ea67c183fe5511ad99aeaae1a32b5245bd020e36Jungshik Jang } 21063a2e0696ce2a04fbe0f1f00cfe9c93189f944daYuncheol Heo} 211