1/*
2 * Copyright (c) 2015, Motorola Mobility LLC
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *     - Redistributions of source code must retain the above copyright
8 *       notice, this list of conditions and the following disclaimer.
9 *     - Redistributions in binary form must reproduce the above copyright
10 *       notice, this list of conditions and the following disclaimer in the
11 *       documentation and/or other materials provided with the distribution.
12 *     - Neither the name of Motorola Mobility nor the
13 *       names of its contributors may be used to endorse or promote products
14 *       derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY LLC BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26 * DAMAGE.
27 */
28
29package com.android.ims;
30
31import android.content.Context;
32import android.content.Intent;
33import android.os.IBinder;
34import android.os.IBinder.DeathRecipient;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.telephony.Rlog;
38
39import com.android.ims.internal.IRcsService;
40import com.android.ims.internal.IRcsPresence;
41
42import java.util.HashMap;
43
44/**
45 * Provides APIs for Rcs services, currently it supports presence only.
46 * This class is the starting point for any RCS actions.
47 * You can acquire an instance of it with {@link #getInstance getInstance()}.
48 *
49 * @hide
50 */
51public class RcsManager {
52    /**
53     * For accessing the RCS related service.
54     * Internal use only.
55     *
56     * @hide
57     */
58    private static final String RCS_SERVICE = "rcs";
59
60    /**
61     * Part of the ACTION_RCS_SERVICE_AVAILABLE and ACTION_RCS_SERVICE_UNAVAILABLE intents.
62     * A long value; the subId corresponding to the RCS service. For MSIM implementation.
63     *
64     * @see #ACTION_RCS_SERVICE_AVAILABLE
65     * @see #ACTION_RCS_SERVICE_UNAVAILABLE
66     */
67    public static final String EXTRA_SUBID = "android:subid";
68
69    /**
70     * Action to broadcast when RcsService is available.
71     *
72     * @see #EXTRA_SUBID
73     */
74    public static final String ACTION_RCS_SERVICE_AVAILABLE =
75            "com.android.ims.ACTION_RCS_SERVICE_AVAILABLE";
76
77    /**
78     * Action to broadcast when RcsService is unavailable (such as ims is not registered).
79     *
80     * @see #EXTRA_SUBID
81     */
82    public static final String ACTION_RCS_SERVICE_UNAVAILABLE =
83            "com.android.ims.ACTION_RCS_SERVICE_UNAVAILABLE";
84
85    /**
86     * Action to broadcast when RcsService is died.
87     * The caller can listen to the intent to clean the pending request.
88     *
89     * It takes the extra parameter subid as well since it depends on OEM implementation for
90     * RcsService. It will not send broadcast for ACTION_RCS_SERVICE_UNAVAILABLE under the case.
91     *
92     * @see #EXTRA_SUBID
93     */
94    public static final String ACTION_RCS_SERVICE_DIED =
95            "com.android.ims.ACTION_RCS_SERVICE_DIED";
96
97    public static class ResultCode {
98        /**
99         * The code is used when the request is success.
100         */
101        public static final int SUCCESS =0;
102
103        /**
104         * Return this code if the service doesn't be enabled on the phone.
105         * As per the requirement the feature can be enabled/disabled by DM.
106         */
107        public static final int ERROR_SERVICE_NOT_ENABLED = -1;
108
109        /**
110         * Return this code if the service didn't publish yet.
111         */
112        public static final int ERROR_SERVICE_NOT_PUBLISHED = -2;
113
114        /**
115         * The service is not available, for example it is 1x only
116         */
117        public static final int ERROR_SERVICE_NOT_AVAILABLE = -3;
118
119        /**
120         *  SUBSCRIBE Error base
121         */
122        public static final int SUBSCRIBER_ERROR_CODE_START = ERROR_SERVICE_NOT_AVAILABLE;
123
124        /**
125         * Temporary error and need retry later.
126         * such as:
127         * 503 Service Unavailable
128         * Device shall retry with exponential back-off
129         *
130         * 408 Request Timeout
131         * Device shall retry with exponential back-off
132         *
133         * 423 Interval Too Short. Requested expiry interval too short and server rejects it
134         * Device shall re-attempt subscription after changing the expiration interval in
135         * the Expires header field to be equal to or greater than the expiration interval
136         * within the Min-Expires header field of the 423 response
137         */
138        public static final int SUBSCRIBE_TEMPORARY_ERROR = SUBSCRIBER_ERROR_CODE_START - 1;
139
140        /**
141         * receives 403 (reason="User Not Registered").
142         * Re-Register to IMS then retry the single resource subscription if capability polling.
143         * availability fetch: no retry.
144         */
145         public static final int SUBSCRIBE_NOT_REGISTERED = SUBSCRIBER_ERROR_CODE_START - 2;
146
147        /**
148         * Responding for 403 - not authorized (Requestor)
149         * No retry.
150         */
151        public static final int SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE =
152                SUBSCRIBER_ERROR_CODE_START - 3;
153
154        /**
155         * Responding for "403 Forbidden" or "403"
156         * Handle it as same as 404 Not found.
157         * No retry.
158         */
159        public static final int SUBSCRIBE_FORBIDDEN = SUBSCRIBER_ERROR_CODE_START - 4;
160
161        /**
162         * Responding for 404 (target number)
163         * No retry.
164         */
165        public static final int SUBSCRIBE_NOT_FOUND = SUBSCRIBER_ERROR_CODE_START - 5;
166
167        /**
168         *  Responding for 413 - Too Large. Top app need shrink the size
169         *  of request contact list and resend the request
170         */
171        public static final int SUBSCRIBE_TOO_LARGE = SUBSCRIBER_ERROR_CODE_START - 6;
172
173        /**
174         * All subscribe errors not covered by specific errors
175         * Other 4xx/5xx/6xx
176         *
177         * Device shall not retry
178         */
179        public static final int SUBSCRIBE_GENIRIC_FAILURE = SUBSCRIBER_ERROR_CODE_START - 7;
180
181        /**
182         * Invalid parameter - The caller should check the parameter.
183         */
184        public static final int SUBSCRIBE_INVALID_PARAM = SUBSCRIBER_ERROR_CODE_START - 8;
185
186        /**
187         * Fetch error - The RCS statck failed to fetch the presence information.
188         */
189        public static final int SUBSCRIBE_FETCH_ERROR = SUBSCRIBER_ERROR_CODE_START - 9;
190
191        /**
192         * Request timeout - The RCS statck returns timeout error.
193         */
194        public static final int SUBSCRIBE_REQUEST_TIMEOUT = SUBSCRIBER_ERROR_CODE_START - 10;
195
196        /**
197         * Insufficient memory - The RCS statck returns the insufficient memory error.
198         */
199        public static final int SUBSCRIBE_INSUFFICIENT_MEMORY = SUBSCRIBER_ERROR_CODE_START - 11;
200
201        /**
202         * Lost network error - The RCS statck returns the lost network error.
203         */
204        public static final int SUBSCRIBE_LOST_NETWORK = SUBSCRIBER_ERROR_CODE_START - 12;
205
206        /**
207         * Not supported error - The RCS statck returns the not supported error.
208         */
209        public static final int SUBSCRIBE_NOT_SUPPORTED = SUBSCRIBER_ERROR_CODE_START - 13;
210
211        /**
212         * Generic error - RCS Presence stack returns generic error
213         */
214        public static final int SUBSCRIBE_GENERIC = SUBSCRIBER_ERROR_CODE_START - 14;
215
216        /**
217         * There is a request for the same number in queue.
218         */
219        public static final int SUBSCRIBE_ALREADY_IN_QUEUE = SUBSCRIBER_ERROR_CODE_START - 16;
220
221        /**
222         * Request too frequently.
223         */
224        public static final int SUBSCRIBE_TOO_FREQUENTLY = SUBSCRIBER_ERROR_CODE_START - 17;
225
226        /**
227         *  The last Subscriber error code
228         */
229        public static final int SUBSCRIBER_ERROR_CODE_END = SUBSCRIBER_ERROR_CODE_START - 17;
230    };
231
232    private static final String TAG = "RcsManager";
233    private static final boolean DBG = true;
234
235    private static HashMap<Integer, RcsManager> sRcsManagerInstances =
236            new HashMap<Integer, RcsManager>();
237
238    private Context mContext;
239    private int mSubId;
240    private IRcsService mRcsService = null;
241    private RcsServiceDeathRecipient mDeathRecipient = new RcsServiceDeathRecipient();
242
243    // Interface for presence
244    // TODO: Could add other RCS service such RcsChat, RcsFt later.
245    private RcsPresence  mRcsPresence = null;
246
247    /**
248     * Gets a manager instance.
249     *
250     * @param context application context for creating the manager object
251     * @param subId the subscription ID for the RCS Service
252     * @return the manager instance corresponding to the subId
253     */
254    public static RcsManager getInstance(Context context, int subId) {
255        synchronized (sRcsManagerInstances) {
256            if (sRcsManagerInstances.containsKey(subId)){
257                return sRcsManagerInstances.get(subId);
258            }
259
260            RcsManager mgr = new RcsManager(context, subId);
261            sRcsManagerInstances.put(subId, mgr);
262
263            return mgr;
264        }
265    }
266
267    private RcsManager(Context context, int subId) {
268        mContext = context;
269        mSubId = subId;
270        createRcsService(true);
271    }
272
273    /**
274     * return true if the rcs service is ready for use.
275     */
276    public boolean isRcsServiceAvailable() {
277        if (DBG) Rlog.d(TAG, "call isRcsServiceAvailable ...");
278
279        boolean ret = false;
280
281        try {
282            checkAndThrowExceptionIfServiceUnavailable();
283
284            ret = mRcsService.isRcsServiceAvailable();
285        }  catch (RemoteException e) {
286            // return false under the case.
287            Rlog.e(TAG, "isRcsServiceAvailable RemoteException", e);
288        }catch (RcsException e){
289            // return false under the case.
290            Rlog.e(TAG, "isRcsServiceAvailable RcsException", e);
291        }
292
293        if (DBG) Rlog.d(TAG, "isRcsServiceAvailable ret =" + ret);
294        return ret;
295    }
296
297    /**
298     * Gets the presence interface
299     *
300     * @return the RcsPresence instance.
301     * @throws  if getting the RcsPresence interface results in an error.
302     */
303    public RcsPresence getRcsPresenceInterface() throws RcsException {
304
305        if (mRcsPresence == null) {
306            checkAndThrowExceptionIfServiceUnavailable();
307
308            try {
309                IRcsPresence rcsPresence = mRcsService.getRcsPresenceInterface();
310                if (rcsPresence == null) {
311                    throw new RcsException("getRcsPresenceInterface()",
312                            ResultCode.ERROR_SERVICE_NOT_AVAILABLE);
313                }
314                mRcsPresence = new RcsPresence(rcsPresence);
315            } catch (RemoteException e) {
316                throw new RcsException("getRcsPresenceInterface()", e,
317                        ResultCode.ERROR_SERVICE_NOT_AVAILABLE);
318            }
319        }
320        if (DBG) Rlog.d(TAG, "getRcsPresenceInterface(), mRcsPresence= " + mRcsPresence);
321        return mRcsPresence;
322    }
323
324    /**
325     * Binds the RCS service only if the service is not created.
326     */
327    private void checkAndThrowExceptionIfServiceUnavailable()
328            throws  RcsException {
329        if (mRcsService == null) {
330            createRcsService(true);
331
332            if (mRcsService == null) {
333                throw new RcsException("Service is unavailable",
334                        ResultCode.ERROR_SERVICE_NOT_AVAILABLE);
335            }
336        }
337    }
338
339    private static String getRcsServiceName(int subId) {
340        // use the same mechanism as IMS_SERVICE?
341        return RCS_SERVICE;
342    }
343
344    /**
345     * Binds the RCS service.
346     */
347    private void createRcsService(boolean checkService) {
348        if (checkService) {
349            IBinder binder = ServiceManager.checkService(getRcsServiceName(mSubId));
350
351            if (binder == null) {
352                return;
353            }
354        }
355
356        IBinder b = ServiceManager.getService(getRcsServiceName(mSubId));
357
358        if (b != null) {
359            try {
360                b.linkToDeath(mDeathRecipient, 0);
361            } catch (RemoteException e) {
362            }
363        }
364
365        mRcsService = IRcsService.Stub.asInterface(b);
366    }
367
368    /**
369     * Death recipient class for monitoring RCS service.
370     */
371    private class RcsServiceDeathRecipient implements IBinder.DeathRecipient {
372        @Override
373        public void binderDied() {
374            mRcsService = null;
375            mRcsPresence = null;
376
377            if (mContext != null) {
378                Intent intent = new Intent(ACTION_RCS_SERVICE_DIED);
379                intent.putExtra(EXTRA_SUBID, mSubId);
380                mContext.sendBroadcast(new Intent(intent));
381            }
382        }
383    }
384}
385