1 /* 2 * Copyright (C) 2022 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 package com.android.providers.contacts.util; 17 18 import com.android.providers.contacts.CallLogDatabaseHelper; 19 import com.android.providers.contacts.ContactsDatabaseHelper; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.content.SharedPreferences; 23 import android.provider.CallLog; 24 import android.provider.CallLog.Calls; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.database.Cursor; 27 import android.database.DatabaseUtils; 28 import android.provider.ContactsContract.Data; 29 import android.preference.PreferenceManager; 30 import android.telephony.SubscriptionManager; 31 import android.telephony.SubscriptionInfo; 32 import android.text.TextUtils; 33 import android.util.Log; 34 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 39 /** 40 * Utils for PhoneAccountHandle Migration Operations in database providers. 41 * 42 * When the database is created and upgraded, PhoneAccountHandleMigrationUtils helps migrate IccId 43 * to SubId. If the PhoneAccount haven't registered yet, we set the pending status for further 44 * migration. Databases will listen to broadcast 45 * {@link android.telecom.TelecomManager#ACTION_PHONE_ACCOUNT_REGISTERED} to identify a new sim 46 * event and performing migration for pending status if possible. 47 */ 48 public class PhoneAccountHandleMigrationUtils { 49 /** 50 * Indicates type of ContactsDatabase. 51 */ 52 public static final int TYPE_CONTACTS = 0; 53 /** 54 * Indicates type of CallLogDatabase. 55 */ 56 public static final int TYPE_CALL_LOG = 1; 57 58 public static final String TELEPHONY_COMPONENT_NAME = 59 "com.android.phone/com.android.services.telephony.TelephonyConnectionService"; 60 private static final String[] TAGS = { 61 "PhoneAccountHandleMigrationUtils_ContactsDatabaseHelper", 62 "PhoneAccountHandleMigrationUtils_CallLogDatabaseHelper"}; 63 private static final String[] TABLES = {ContactsDatabaseHelper.Tables.DATA, 64 CallLogDatabaseHelper.Tables.CALLS}; 65 private static final String[] IDS = {Data._ID, Calls._ID}; 66 private static final String[] PENDING_STATUS_FIELDS = { 67 Data.IS_PHONE_ACCOUNT_MIGRATION_PENDING, Calls.IS_PHONE_ACCOUNT_MIGRATION_PENDING}; 68 private static final String[] COMPONENT_NAME_FIELDS = { 69 Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, Calls.PHONE_ACCOUNT_COMPONENT_NAME}; 70 private static final String[] PHONE_ACCOUNT_ID_FIELDS = { 71 Data.PREFERRED_PHONE_ACCOUNT_ID, Calls.PHONE_ACCOUNT_ID}; 72 73 private int mType; 74 private SubscriptionManager mSubscriptionManager; 75 private SharedPreferences mSharedPreferences; 76 77 /** 78 * Constructor of the util. 79 */ PhoneAccountHandleMigrationUtils(Context context, int type)80 public PhoneAccountHandleMigrationUtils(Context context, int type) { 81 mType = type; 82 mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); 83 mSubscriptionManager = context.getSystemService(SubscriptionManager.class); 84 } 85 86 /** 87 * Mark all the telephony phone account handles as pending migration. 88 */ markAllTelephonyPhoneAccountsPendingMigration(SQLiteDatabase db)89 public void markAllTelephonyPhoneAccountsPendingMigration(SQLiteDatabase db) { 90 ContentValues valuesForTelephonyPending = new ContentValues(); 91 valuesForTelephonyPending.put(PENDING_STATUS_FIELDS[mType], 1); 92 String selection = COMPONENT_NAME_FIELDS[mType] + " = ?"; 93 String[] selectionArgs = {TELEPHONY_COMPONENT_NAME}; 94 db.beginTransaction(); 95 try { 96 int count = db.update( 97 TABLES[mType], valuesForTelephonyPending, selection, selectionArgs); 98 Log.i(TAGS[mType], "markAllTelephonyPhoneAccountsPendingMigration count: " + count); 99 db.setTransactionSuccessful(); 100 } finally { 101 db.endTransaction(); 102 } 103 } 104 105 /** 106 * Set phone account migration pending status, indicating if there is any phone account handle 107 * that need to migrate. Store the value in the SharedPreference to prevent the need to query 108 * the database in the future for pending migration. 109 */ setPhoneAccountMigrationStatusPending(boolean status)110 public void setPhoneAccountMigrationStatusPending(boolean status) { 111 mSharedPreferences.edit().putBoolean(PENDING_STATUS_FIELDS[mType], status).apply(); 112 } 113 114 /** 115 * Checks phone account migration pending status, indicating if there is any phone account 116 * handle that need to migrate. Query the value in the SharedPreference to prevent the need 117 * to query the database in the future for pending migration. 118 */ isPhoneAccountMigrationPending()119 public boolean isPhoneAccountMigrationPending() { 120 return mSharedPreferences.getBoolean(PENDING_STATUS_FIELDS[mType], false); 121 } 122 123 /** 124 * Updates phone account migration pending status, indicating if there is any phone account 125 * handle that need to migrate. 126 */ updatePhoneAccountHandleMigrationPendingStatus(SQLiteDatabase sqLiteDatabase)127 public void updatePhoneAccountHandleMigrationPendingStatus(SQLiteDatabase sqLiteDatabase) { 128 // Check to see if any entries need phone account migration pending. 129 long count = DatabaseUtils.longForQuery(sqLiteDatabase, "SELECT COUNT(DISTINCT " 130 + IDS[mType] + ") FROM " + TABLES[mType] + " WHERE " 131 + PENDING_STATUS_FIELDS[mType] + " == 1", null); 132 if (count > 0) { 133 Log.i(TAGS[mType], "updatePhoneAccountHandleMigrationPendingStatus true"); 134 setPhoneAccountMigrationStatusPending(true); 135 } else { 136 Log.i(TAGS[mType], "updatePhoneAccountHandleMigrationPendingStatus false"); 137 setPhoneAccountMigrationStatusPending(false); 138 } 139 } 140 141 /** 142 * Migrate all the pending phone account handles based on the given iccId and subId. 143 */ migratePendingPhoneAccountHandles(String iccId, String subId, SQLiteDatabase db)144 public void migratePendingPhoneAccountHandles(String iccId, String subId, SQLiteDatabase db) { 145 ContentValues valuesForPhoneAccountId = new ContentValues(); 146 valuesForPhoneAccountId.put(PHONE_ACCOUNT_ID_FIELDS[mType], subId); 147 valuesForPhoneAccountId.put(PENDING_STATUS_FIELDS[mType], 0); 148 String selection = PHONE_ACCOUNT_ID_FIELDS[mType] + " LIKE ? AND " 149 + PENDING_STATUS_FIELDS[mType] + " = ?"; 150 String[] selectionArgs = {iccId, "1"}; 151 db.beginTransaction(); 152 try { 153 int count = db.update(TABLES[mType], valuesForPhoneAccountId, selection, selectionArgs); 154 Log.i(TAGS[mType], "migrated pending PhoneAccountHandle subId: " + subId 155 + " count: " + count); 156 db.setTransactionSuccessful(); 157 } finally { 158 db.endTransaction(); 159 } 160 updatePhoneAccountHandleMigrationPendingStatus(db); 161 } 162 163 /** 164 * Try to migrate any PhoneAccountId to SubId from IccId. 165 */ migrateIccIdToSubId(SQLiteDatabase db)166 public void migrateIccIdToSubId(SQLiteDatabase db) { 167 final HashMap<String, String> phoneAccountIdsMigrateNow = new HashMap<>(); 168 final Cursor phoneAccountIdsCursor = db.rawQuery( 169 "SELECT DISTINCT " + PHONE_ACCOUNT_ID_FIELDS[mType] + " FROM " + TABLES[mType] 170 + " WHERE " + PENDING_STATUS_FIELDS[mType] + " = 1", null); 171 172 try { 173 List<SubscriptionInfo> subscriptionInfoList = mSubscriptionManager 174 .getAllSubscriptionInfoList(); 175 phoneAccountIdsCursor.moveToPosition(-1); 176 while (phoneAccountIdsCursor.moveToNext()) { 177 if (phoneAccountIdsCursor.isNull(0)) { 178 continue; 179 } 180 final String iccId = phoneAccountIdsCursor.getString(0); 181 String subId = null; 182 if (mSubscriptionManager != null) { 183 subId = getSubIdForIccId(iccId, subscriptionInfoList); 184 } 185 186 if (!TextUtils.isEmpty(iccId)) { 187 if (subId != null) { 188 // If there is already a subId that maps to the corresponding iccid 189 // from an old phone account handle, migrate to the new phone account 190 // handle with sub id without pending. 191 phoneAccountIdsMigrateNow.put(iccId, subId); 192 Log.i(TAGS[mType], "migrateIccIdToSubId(db): found subId: " + subId); 193 } 194 } 195 } 196 } finally { 197 phoneAccountIdsCursor.close(); 198 } 199 // Migrate to the new phone account handle with its sub ID that is already available. 200 for (Map.Entry<String, String> set : phoneAccountIdsMigrateNow.entrySet()) { 201 migratePendingPhoneAccountHandles(set.getKey(), set.getValue(), db); 202 } 203 } 204 205 // Return a subId that maps to the given iccId, or null if the subId is not available. getSubIdForIccId(String iccId, List<SubscriptionInfo> subscriptionInfoList)206 private String getSubIdForIccId(String iccId, List<SubscriptionInfo> subscriptionInfoList) { 207 for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) { 208 // Some old version callog would store phone account handle id with the IccId 209 // string plus "F", and the getIccId() returns IccId string itself without "F", 210 // so here need to use "startsWith" to match. 211 if (iccId.startsWith(subscriptionInfo.getIccId())) { 212 Log.i(TAGS[mType], "getSubIdForIccId: Found subscription ID to migrate: " 213 + subscriptionInfo.getSubscriptionId()); 214 return Integer.toString(subscriptionInfo.getSubscriptionId()); 215 } 216 } 217 return null; 218 } 219 }