1/*
2 * Copyright (C) 2013 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
17package com.android.phone;
18
19import android.app.PendingIntent;
20import android.app.PendingIntent.CanceledException;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.os.AsyncResult;
26import android.os.Handler;
27import android.os.Message;
28import android.telephony.ServiceState;
29import android.util.Log;
30
31import com.android.internal.telephony.Phone;
32import com.google.common.base.Preconditions;
33
34/**
35 * Starts and displays status for Hands Free Activation (HFA).
36 *
37 * This class operates with Hands Free Activation apps.
38 * It starts by broadcasting the intent com.android.action.START_HFA.
39 * An HFA app will pick that up and start the HFA process.
40 * If it fails it return ERROR_HFA Intent and upon success returns COMPLETE_HFA.
41 *
42 * If successful, we bounce the radio so that the service picks up the new number.
43 * Once the radio is back on we callback the requestor.
44 *
45 * If there is an error, we do not bounce the radio but still callback with a failure.
46 *
47 * TODO(klp): We need system-only permissions for the HFA intents.
48 */
49public class HfaLogic {
50    private static final String TAG = HfaLogic.class.getSimpleName();
51
52    private static final String ACTION_START = "com.android.action.START_HFA";
53    private static final String ACTION_ERROR = "com.android.action.ERROR_HFA";
54    private static final String ACTION_CANCEL = "com.android.action.CANCEL_HFA";
55    private static final String ACTION_COMPLETE = "com.android.action.COMPLETE_HFA";
56
57    private static final int SERVICE_STATE_CHANGED = 1;
58
59    public static final int NOT_WAITING = 0;
60    public static final int WAITING_FOR_RADIO_OFF = 1;
61    public static final int WAITING_FOR_RADIO_ON = 2;
62
63    public static final int OTASP_UNKNOWN = 0;
64    public static final int OTASP_USER_SKIPPED = 1;
65    public static final int OTASP_SUCCESS = 2;
66    public static final int OTASP_FAILURE = 3;
67
68    private int mPhoneMonitorState = NOT_WAITING;
69    private BroadcastReceiver mReceiver;
70    private HfaLogicCallback mCallback;
71    private PendingIntent mResponseIntent;
72    private Context mContext;
73
74    // No retry at the moment. Increase later if necessary.
75    private static final int DEFAULT_RETRY_COUNT = 0;
76    private int mRetryCount;
77
78    public interface HfaLogicCallback {
79        public void onSuccess();
80        public void onError(String errorMsg);
81    }
82
83    public HfaLogic(Context context, HfaLogicCallback callback, PendingIntent intent) {
84        mCallback = Preconditions.checkNotNull(callback);
85        mContext = Preconditions.checkNotNull(context);
86        mResponseIntent = intent;
87    }
88
89    public void start() {
90        Log.i(TAG, "start:");
91        mRetryCount = DEFAULT_RETRY_COUNT;
92        startHfaIntentReceiver();
93        startProvisioning();
94    }
95
96    private void startProvisioning() {
97        Log.i(TAG, "startProvisioning:");
98        sendHfaCommand(ACTION_START);
99    }
100
101    private void sendHfaCommand(String action) {
102        Log.i(TAG, "sendHfaCommand: command=" + action);
103        mContext.sendBroadcast(new Intent(action));
104    }
105
106    private void onHfaError(String errorMsg) {
107        Log.i(TAG, "onHfaError: call mCallBack.onError errorMsg=" + errorMsg
108                + " mRetryCount=" + mRetryCount);
109        mRetryCount -= 1;
110        if (mRetryCount >= 0) {
111            Log.i(TAG, "onHfaError: retry");
112            startProvisioning();
113        } else {
114            Log.i(TAG, "onHfaError: Declare OTASP_FAILURE");
115            mRetryCount = 0;
116            stopHfaIntentReceiver();
117            sendFinalResponse(OTASP_FAILURE, errorMsg);
118            mCallback.onError(errorMsg);
119        }
120    }
121
122    private void onHfaSuccess() {
123        Log.i(TAG, "onHfaSuccess: NOT bouncing radio call onTotalSuccess");
124        stopHfaIntentReceiver();
125        // bounceRadio();
126        onTotalSuccess();
127    }
128
129    private void onTotalSuccess() {
130        Log.i(TAG, "onTotalSuccess: call mCallBack.onSuccess");
131        sendFinalResponse(OTASP_SUCCESS, null);
132        mCallback.onSuccess();
133    }
134
135    private void bounceRadio() {
136        final Phone phone = PhoneGlobals.getInstance().getPhone();
137        phone.registerForServiceStateChanged(mHandler, SERVICE_STATE_CHANGED, null);
138
139        mPhoneMonitorState = WAITING_FOR_RADIO_OFF;
140        phone.setRadioPower(false);
141        onServiceStateChange(phone.getServiceState());
142    }
143
144    private void onServiceStateChange(ServiceState state) {
145        final boolean radioIsOff = state.getVoiceRegState() == ServiceState.STATE_POWER_OFF;
146        final Phone phone = PhoneGlobals.getInstance().getPhone();
147
148        Log.i(TAG, "Radio is on: " + !radioIsOff);
149
150        if (mPhoneMonitorState == WAITING_FOR_RADIO_OFF) {
151            if (radioIsOff) {
152                mPhoneMonitorState = WAITING_FOR_RADIO_ON;
153                phone.setRadioPower(true);
154            }
155        } else if (mPhoneMonitorState == WAITING_FOR_RADIO_ON) {
156            if (!radioIsOff) {
157                mPhoneMonitorState = NOT_WAITING;
158                phone.unregisterForServiceStateChanged(mHandler);
159
160                onTotalSuccess();
161            }
162        }
163    }
164
165    private void startHfaIntentReceiver() {
166        final IntentFilter filter = new IntentFilter(ACTION_COMPLETE);
167        filter.addAction(ACTION_ERROR);
168
169        mReceiver = new BroadcastReceiver() {
170            @Override
171            public void onReceive(Context context, Intent intent) {
172                final String action = intent.getAction();
173                if (action.equals(ACTION_ERROR)) {
174                    onHfaError(intent.getStringExtra("errorCode"));
175                } else if (action.equals(ACTION_COMPLETE)) {
176                    Log.i(TAG, "Hfa Successful");
177                    onHfaSuccess();
178                }
179            }
180        };
181
182        mContext.registerReceiver(mReceiver, filter);
183    }
184
185    private void stopHfaIntentReceiver() {
186        if (mReceiver != null) {
187            mContext.unregisterReceiver(mReceiver);
188            mReceiver = null;
189        }
190    }
191
192    private void sendFinalResponse(int responseCode, String errorCode) {
193        if (mResponseIntent != null) {
194            final Intent extraStuff = new Intent();
195            extraStuff.putExtra(OtaUtils.EXTRA_OTASP_RESULT_CODE, responseCode);
196
197            if (responseCode == OTASP_FAILURE && errorCode != null) {
198                extraStuff.putExtra(OtaUtils.EXTRA_OTASP_ERROR_CODE, errorCode);
199            }
200
201            try {
202                Log.i(TAG, "Sending OTASP confirmation with result code: "
203                        + responseCode);
204                mResponseIntent.send(mContext, 0 /* resultCode (not used) */, extraStuff);
205            } catch (CanceledException e) {
206                Log.e(TAG, "Pending Intent canceled");
207            }
208        }
209    }
210
211    private Handler mHandler = new Handler() {
212        @Override
213        public void handleMessage(Message msg) {
214            switch (msg.what) {
215                case SERVICE_STATE_CHANGED:
216                    ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
217                    onServiceStateChange(state);
218                    break;
219                default:
220                    break;
221            }
222        }
223    };
224
225}
226