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 @Override onCreate()72 public void onCreate() { 73 super.onCreate(); 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 88 @Override onStartJob(JobParameters params)89 public boolean onStartJob(JobParameters params) { 90 mOngoingTask = new AsyncTask<JobParameters, Void, Void>() { 91 @Override 92 protected Void doInBackground(JobParameters... params) { 93 onHandleWork(params[0]); 94 return null; 95 } 96 }; 97 mOngoingTask.execute(params); 98 return true; 99 } 100 101 @Override onStopJob(JobParameters params)102 public boolean onStopJob(JobParameters params) { 103 return true; // Always re-run if job stopped. 104 } 105 106 /** 107 * Registers to receive FCM messages published to subscribe topics under the retrieved token. 108 * The token changes when the InstanceID becomes invalid (e.g. app data is deleted). 109 */ onHandleWork(JobParameters params)110 protected void onHandleWork(JobParameters params) { 111 boolean wantsReschedule = false; 112 FirebaseInstanceId instanceID = getFirebaseInstanceId(); 113 if (instanceID == null) { 114 Log.d(TAG, "Cannot get fcm token because FirebaseInstanceId is null"); 115 return; 116 } 117 for (int subId : TelephonyUtils.getSubIdsWithFcmSupported(this)) { 118 if (!updateFcmToken(instanceID, subId)) { 119 wantsReschedule = true; 120 } 121 } 122 123 jobFinished(params, wantsReschedule); 124 } 125 126 /** Returns {@code false} if failed to get token. */ updateFcmToken(FirebaseInstanceId instanceID, int subId)127 private boolean updateFcmToken(FirebaseInstanceId instanceID, int subId) { 128 Log.d(TAG, "FcmRegistrationService.updateFcmToken: subId=" + subId); 129 String token = getTokenForSubId(instanceID, subId); 130 if (token == null) { 131 Log.d(TAG, "getToken null"); 132 return false; 133 } 134 Log.d(TAG, "FCM token: " + token + " subId: " + subId); 135 FcmTokenStore.setToken(this, subId, token); 136 return true; 137 } 138 getFirebaseInstanceId()139 private FirebaseInstanceId getFirebaseInstanceId() { 140 return (mFakeInstanceID != null) ? mFakeInstanceID : FirebaseInstanceId.getInstance(mApp); 141 } 142 getTokenForSubId(FirebaseInstanceId instanceID, int subId)143 private String getTokenForSubId(FirebaseInstanceId instanceID, int subId) { 144 String token = null; 145 try { 146 token = instanceID.getToken( 147 TelephonyUtils.getFcmSenderId(this, subId), 148 FirebaseMessaging.INSTANCE_ID_SCOPE); 149 } catch (IOException e) { 150 Log.e(TAG, "Failed to get a new FCM token: " + e); 151 } 152 return token; 153 } 154 } 155