1 /* 2 * Copyright (C) 2015 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.voicemail.impl.sync; 17 18 import android.annotation.TargetApi; 19 import android.content.Context; 20 import android.os.Build.VERSION_CODES; 21 import android.os.UserManager; 22 import android.support.annotation.MainThread; 23 import android.support.annotation.NonNull; 24 import android.support.annotation.VisibleForTesting; 25 import android.telecom.PhoneAccountHandle; 26 import android.telecom.TelecomManager; 27 import android.util.ArraySet; 28 import com.android.dialer.common.Assert; 29 import com.android.dialer.common.PerAccountSharedPreferences; 30 import com.android.dialer.common.concurrent.ThreadUtil; 31 import com.android.dialer.storage.StorageComponent; 32 import com.android.voicemail.VoicemailClient.ActivationStateListener; 33 import com.android.voicemail.impl.OmtpConstants; 34 import com.android.voicemail.impl.VisualVoicemailPreferences; 35 import com.android.voicemail.impl.VoicemailStatus; 36 import com.android.voicemail.impl.sms.StatusMessage; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Set; 40 41 /** 42 * Tracks the activation state of a visual voicemail phone account. An account is considered 43 * activated if it has valid connection information from the {@link StatusMessage} stored on the 44 * device. Once activation/provisioning is completed, {@link #addAccount(Context, 45 * PhoneAccountHandle, StatusMessage)} should be called to store the connection information. When an 46 * account is removed or if the connection information is deemed invalid, {@link 47 * #removeAccount(Context, PhoneAccountHandle)} should be called to clear the connection information 48 * and allow reactivation. 49 */ 50 @TargetApi(VERSION_CODES.O) 51 public class VvmAccountManager { 52 public static final String TAG = "VvmAccountManager"; 53 54 @VisibleForTesting static final String IS_ACCOUNT_ACTIVATED = "is_account_activated"; 55 56 private static final Set<ActivationStateListener> listeners = new ArraySet<>(); 57 addAccount( Context context, PhoneAccountHandle phoneAccountHandle, StatusMessage statusMessage)58 public static void addAccount( 59 Context context, PhoneAccountHandle phoneAccountHandle, StatusMessage statusMessage) { 60 VisualVoicemailPreferences preferences = 61 new VisualVoicemailPreferences(context, phoneAccountHandle); 62 statusMessage.putStatus(preferences.edit()).apply(); 63 setAccountActivated(context, phoneAccountHandle, true); 64 65 ThreadUtil.postOnUiThread( 66 () -> { 67 for (ActivationStateListener listener : listeners) { 68 listener.onActivationStateChanged(phoneAccountHandle, true); 69 } 70 }); 71 } 72 removeAccount(Context context, PhoneAccountHandle phoneAccount)73 public static void removeAccount(Context context, PhoneAccountHandle phoneAccount) { 74 VoicemailStatus.disable(context, phoneAccount); 75 setAccountActivated(context, phoneAccount, false); 76 VisualVoicemailPreferences preferences = new VisualVoicemailPreferences(context, phoneAccount); 77 preferences 78 .edit() 79 .putString(OmtpConstants.IMAP_USER_NAME, null) 80 .putString(OmtpConstants.IMAP_PASSWORD, null) 81 .apply(); 82 ThreadUtil.postOnUiThread( 83 () -> { 84 for (ActivationStateListener listener : listeners) { 85 listener.onActivationStateChanged(phoneAccount, false); 86 } 87 }); 88 } 89 isAccountActivated(Context context, PhoneAccountHandle phoneAccount)90 public static boolean isAccountActivated(Context context, PhoneAccountHandle phoneAccount) { 91 Assert.isNotNull(phoneAccount); 92 PerAccountSharedPreferences preferences = 93 getPreferenceForActivationState(context, phoneAccount); 94 migrateActivationState(context, preferences, phoneAccount); 95 return preferences.getBoolean(IS_ACCOUNT_ACTIVATED, false); 96 } 97 98 @NonNull getActiveAccounts(Context context)99 public static List<PhoneAccountHandle> getActiveAccounts(Context context) { 100 List<PhoneAccountHandle> results = new ArrayList<>(); 101 for (PhoneAccountHandle phoneAccountHandle : 102 context.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts()) { 103 if (isAccountActivated(context, phoneAccountHandle)) { 104 results.add(phoneAccountHandle); 105 } 106 } 107 return results; 108 } 109 110 @MainThread addListener(ActivationStateListener listener)111 public static void addListener(ActivationStateListener listener) { 112 Assert.isMainThread(); 113 listeners.add(listener); 114 } 115 116 @MainThread removeListener(ActivationStateListener listener)117 public static void removeListener(ActivationStateListener listener) { 118 Assert.isMainThread(); 119 listeners.remove(listener); 120 } 121 122 /** 123 * The activation state is moved from credential protected storage to device protected storage 124 * after v10, so it can be checked under FBE. The state should be migrated to avoid reactivation. 125 */ migrateActivationState( Context context, PerAccountSharedPreferences deviceProtectedPreference, PhoneAccountHandle phoneAccountHandle)126 private static void migrateActivationState( 127 Context context, 128 PerAccountSharedPreferences deviceProtectedPreference, 129 PhoneAccountHandle phoneAccountHandle) { 130 if (!context.getSystemService(UserManager.class).isUserUnlocked()) { 131 return; 132 } 133 if (deviceProtectedPreference.contains(IS_ACCOUNT_ACTIVATED)) { 134 return; 135 } 136 137 PerAccountSharedPreferences credentialProtectedPreference = 138 new VisualVoicemailPreferences(context, phoneAccountHandle); 139 140 deviceProtectedPreference 141 .edit() 142 .putBoolean( 143 IS_ACCOUNT_ACTIVATED, 144 credentialProtectedPreference.getBoolean(IS_ACCOUNT_ACTIVATED, false)) 145 .apply(); 146 } 147 setAccountActivated( Context context, PhoneAccountHandle phoneAccountHandle, boolean activated)148 private static void setAccountActivated( 149 Context context, PhoneAccountHandle phoneAccountHandle, boolean activated) { 150 Assert.isNotNull(phoneAccountHandle); 151 getPreferenceForActivationState(context, phoneAccountHandle) 152 .edit() 153 .putBoolean(IS_ACCOUNT_ACTIVATED, activated) 154 .apply(); 155 } 156 getPreferenceForActivationState( Context context, PhoneAccountHandle phoneAccountHandle)157 private static PerAccountSharedPreferences getPreferenceForActivationState( 158 Context context, PhoneAccountHandle phoneAccountHandle) { 159 return new PerAccountSharedPreferences( 160 context, phoneAccountHandle, StorageComponent.get(context).unencryptedSharedPrefs()); 161 } 162 } 163