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