1 /*
2  * Copyright (C) 2021 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 com.android.imsserviceentitlement.fcm;
18 
19 import android.app.job.JobParameters;
20 import android.app.job.JobService;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.os.AsyncTask;
24 import android.telephony.SubscriptionManager;
25 import android.util.Log;
26 
27 import androidx.annotation.VisibleForTesting;
28 
29 import com.android.imsserviceentitlement.R;
30 import com.android.imsserviceentitlement.job.JobManager;
31 import com.android.imsserviceentitlement.utils.TelephonyUtils;
32 
33 import com.google.firebase.FirebaseApp;
34 import com.google.firebase.FirebaseOptions;
35 import com.google.firebase.iid.FirebaseInstanceId;
36 import com.google.firebase.messaging.FirebaseMessaging;
37 
38 import java.io.IOException;
39 
40 /** A {@link JobService} that gets a FCM tokens for all active SIMs. */
41 public class FcmRegistrationService extends JobService {
42     private static final String TAG = "IMSSE-FcmRegistrationService";
43 
44     private FirebaseInstanceId mFakeInstanceID = null;
45     private FirebaseApp mApp = null;
46 
47     @VisibleForTesting AsyncTask<JobParameters, Void, Void> mOngoingTask;
48 
49     /** Enqueues a job for FCM registration. */
enqueueJob(Context context)50     public static void enqueueJob(Context context) {
51         ComponentName componentName = new ComponentName(context, FcmRegistrationService.class);
52         // No subscription id associated job, use {@link
53         // SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
54         JobManager jobManager =
55                 JobManager.getInstance(
56                         context, componentName, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
57         jobManager.registerFcmOnceNetworkReady();
58     }
59 
60     @VisibleForTesting
setFakeInstanceID(FirebaseInstanceId instanceID)61     void setFakeInstanceID(FirebaseInstanceId instanceID) {
62         mFakeInstanceID = instanceID;
63     }
64 
65     @Override
66     @VisibleForTesting
attachBaseContext(Context base)67     protected void attachBaseContext(Context base) {
68         super.attachBaseContext(base);
69     }
70 
71     /** Returns a {@link FirebaseApp} instance, lazily initialized. */
getFirebaseApp()72     private FirebaseApp getFirebaseApp() {
73         if (mApp == null) {
74             try {
75                 mApp = FirebaseApp.getInstance();
76             } catch (IllegalStateException e) {
77                 Log.d(TAG, "initialize FirebaseApp");
78                 mApp = FirebaseApp.initializeApp(
79                         this,
80                         new FirebaseOptions.Builder()
81                                 .setApplicationId(getResources().getString(R.string.fcm_app_id))
82                                 .setProjectId(getResources().getString(R.string.fcm_project_id))
83                                 .setApiKey(getResources().getString(R.string.fcm_api_key))
84                                 .build());
85             }
86         }
87         return mApp;
88     }
89 
90     @Override
onStartJob(JobParameters params)91     public boolean onStartJob(JobParameters params) {
92         mOngoingTask = new AsyncTask<JobParameters, Void, Void>() {
93             @Override
94             protected Void doInBackground(JobParameters... params) {
95                 onHandleWork(params[0]);
96                 return null;
97             }
98         };
99         mOngoingTask.execute(params);
100         return true;
101     }
102 
103     @Override
onStopJob(JobParameters params)104     public boolean onStopJob(JobParameters params) {
105         return true; // Always re-run if job stopped.
106     }
107 
108     /**
109      * Registers to receive FCM messages published to subscribe topics under the retrieved token.
110      * The token changes when the InstanceID becomes invalid (e.g. app data is deleted).
111      */
onHandleWork(JobParameters params)112     protected void onHandleWork(JobParameters params) {
113         boolean wantsReschedule = false;
114         for (int subId : TelephonyUtils.getSubIdsWithFcmSupported(this)) {
115             if (!updateFcmToken(getFirebaseInstanceId(), subId)) {
116                 wantsReschedule = true;
117             }
118         }
119 
120         jobFinished(params, wantsReschedule);
121     }
122 
123     /** Returns {@code false} if failed to get token. */
updateFcmToken(FirebaseInstanceId instanceID, int subId)124     private boolean updateFcmToken(FirebaseInstanceId instanceID, int subId) {
125         Log.d(TAG, "FcmRegistrationService.updateFcmToken: subId=" + subId);
126         String token = getTokenForSubId(instanceID, subId);
127         if (token == null) {
128             Log.d(TAG, "getToken null");
129             return false;
130         }
131         Log.d(TAG, "FCM token: " + token + " subId: " + subId);
132         FcmTokenStore.setToken(this, subId, token);
133         return true;
134     }
135 
getFirebaseInstanceId()136     private FirebaseInstanceId getFirebaseInstanceId() {
137         return (mFakeInstanceID != null)
138                 ? mFakeInstanceID
139                 : FirebaseInstanceId.getInstance(getFirebaseApp());
140     }
141 
getTokenForSubId(FirebaseInstanceId instanceID, int subId)142     private String getTokenForSubId(FirebaseInstanceId instanceID, int subId) {
143         String token = null;
144         try {
145             token = instanceID.getToken(
146                     TelephonyUtils.getFcmSenderId(this, subId),
147                     FirebaseMessaging.INSTANCE_ID_SCOPE);
148         } catch (IOException e) {
149             Log.e(TAG, "Failed to get a new FCM token: " + e);
150         }
151         return token;
152     }
153 }
154