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