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