Home | History | Annotate | Download | only in sync
      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 
     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 
     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 
     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
     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
    111   public static void addListener(ActivationStateListener listener) {
    112     Assert.isMainThread();
    113     listeners.add(listener);
    114   }
    115 
    116   @MainThread
    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    */
    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 
    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 
    157   private static PerAccountSharedPreferences getPreferenceForActivationState(
    158       Context context, PhoneAccountHandle phoneAccountHandle) {
    159     return new PerAccountSharedPreferences(
    160         context, phoneAccountHandle, StorageComponent.get(context).unencryptedSharedPrefs());
    161   }
    162 }
    163