1 /* 2 * Copyright (C) 2018 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 17 package com.android.dialer.calllog.config; 18 19 import android.annotation.SuppressLint; 20 import android.app.job.JobInfo; 21 import android.app.job.JobParameters; 22 import android.app.job.JobScheduler; 23 import android.app.job.JobService; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.SharedPreferences; 27 import android.support.v4.os.UserManagerCompat; 28 import com.android.dialer.calllog.CallLogFramework; 29 import com.android.dialer.common.Assert; 30 import com.android.dialer.common.LogUtil; 31 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; 32 import com.android.dialer.common.concurrent.ThreadUtil; 33 import com.android.dialer.configprovider.ConfigProvider; 34 import com.android.dialer.constants.ScheduledJobIds; 35 import com.android.dialer.inject.ApplicationContext; 36 import com.android.dialer.storage.Unencrypted; 37 import com.google.common.util.concurrent.FutureCallback; 38 import com.google.common.util.concurrent.Futures; 39 import com.google.common.util.concurrent.ListenableFuture; 40 import com.google.common.util.concurrent.ListeningExecutorService; 41 import com.google.common.util.concurrent.MoreExecutors; 42 import java.util.concurrent.TimeUnit; 43 import javax.inject.Inject; 44 45 /** 46 * Determines if new call log components are enabled. 47 * 48 * <p>When the underlying flag values from the {@link ConfigProvider} changes, it is necessary to do 49 * work such as registering/unregistering content observers, and this class is responsible for 50 * coordinating that work. 51 * 52 * <p>New UI application components should use this class instead of reading flags directly from the 53 * {@link ConfigProvider}. 54 */ 55 public final class CallLogConfigImpl implements CallLogConfig { 56 57 private static final String NEW_CALL_LOG_FRAGMENT_ENABLED_PREF_KEY = "newCallLogFragmentEnabled"; 58 private static final String NEW_VOICEMAIL_FRAGMENT_ENABLED_PREF_KEY = 59 "newVoicemailFragmentEnabled"; 60 private static final String NEW_PEER_ENABLED_PREF_KEY = "newPeerEnabled"; 61 private static final String NEW_CALL_LOG_FRAMEWORK_ENABLED_PREF_KEY = 62 "newCallLogFrameworkEnabled"; 63 64 private final Context appContext; 65 private final CallLogFramework callLogFramework; 66 private final SharedPreferences sharedPreferences; 67 private final ConfigProvider configProvider; 68 private final ListeningExecutorService backgroundExecutor; 69 70 @Inject CallLogConfigImpl( @pplicationContext Context appContext, CallLogFramework callLogFramework, @Unencrypted SharedPreferences sharedPreferences, ConfigProvider configProvider, @BackgroundExecutor ListeningExecutorService backgroundExecutor)71 public CallLogConfigImpl( 72 @ApplicationContext Context appContext, 73 CallLogFramework callLogFramework, 74 @Unencrypted SharedPreferences sharedPreferences, 75 ConfigProvider configProvider, 76 @BackgroundExecutor ListeningExecutorService backgroundExecutor) { 77 this.appContext = appContext; 78 this.callLogFramework = callLogFramework; 79 this.sharedPreferences = sharedPreferences; 80 this.configProvider = configProvider; 81 this.backgroundExecutor = backgroundExecutor; 82 } 83 84 @Override update()85 public ListenableFuture<Void> update() { 86 boolean newCallLogFragmentEnabledInConfigProvider = 87 configProvider.getBoolean("new_call_log_fragment_enabled", false); 88 boolean newVoicemailFragmentEnabledInConfigProvider = 89 configProvider.getBoolean("new_voicemail_fragment_enabled", false); 90 boolean newPeerEnabledInConfigProvider = configProvider.getBoolean("nui_peer_enabled", false); 91 92 boolean isCallLogFrameworkEnabled = isCallLogFrameworkEnabled(); 93 boolean callLogFrameworkShouldBeEnabled = 94 newCallLogFragmentEnabledInConfigProvider 95 || newVoicemailFragmentEnabledInConfigProvider 96 || newPeerEnabledInConfigProvider; 97 98 if (callLogFrameworkShouldBeEnabled && !isCallLogFrameworkEnabled) { 99 return Futures.transform( 100 callLogFramework.enable(), 101 unused -> { 102 // Reflect the flag changes only after the framework is enabled. 103 sharedPreferences 104 .edit() 105 .putBoolean( 106 NEW_CALL_LOG_FRAGMENT_ENABLED_PREF_KEY, 107 newCallLogFragmentEnabledInConfigProvider) 108 .putBoolean( 109 NEW_VOICEMAIL_FRAGMENT_ENABLED_PREF_KEY, 110 newVoicemailFragmentEnabledInConfigProvider) 111 .putBoolean(NEW_PEER_ENABLED_PREF_KEY, newPeerEnabledInConfigProvider) 112 .putBoolean(NEW_CALL_LOG_FRAMEWORK_ENABLED_PREF_KEY, true) 113 .apply(); 114 return null; 115 }, 116 backgroundExecutor); 117 } else if (!callLogFrameworkShouldBeEnabled && isCallLogFrameworkEnabled) { 118 // Reflect the flag changes before disabling the framework. 119 ListenableFuture<Void> writeSharedPrefsFuture = 120 backgroundExecutor.submit( 121 () -> { 122 sharedPreferences 123 .edit() 124 .putBoolean(NEW_CALL_LOG_FRAGMENT_ENABLED_PREF_KEY, false) 125 .putBoolean(NEW_VOICEMAIL_FRAGMENT_ENABLED_PREF_KEY, false) 126 .putBoolean(NEW_PEER_ENABLED_PREF_KEY, false) 127 .putBoolean(NEW_CALL_LOG_FRAMEWORK_ENABLED_PREF_KEY, false) 128 .apply(); 129 return null; 130 }); 131 return Futures.transformAsync( 132 writeSharedPrefsFuture, 133 unused -> callLogFramework.disable(), 134 MoreExecutors.directExecutor()); 135 } else { 136 // We didn't need to enable/disable the framework, but we still need to update the 137 // individual flags. 138 return backgroundExecutor.submit( 139 () -> { 140 sharedPreferences 141 .edit() 142 .putBoolean( 143 NEW_CALL_LOG_FRAGMENT_ENABLED_PREF_KEY, 144 newCallLogFragmentEnabledInConfigProvider) 145 .putBoolean( 146 NEW_VOICEMAIL_FRAGMENT_ENABLED_PREF_KEY, 147 newVoicemailFragmentEnabledInConfigProvider) 148 .putBoolean(NEW_PEER_ENABLED_PREF_KEY, newPeerEnabledInConfigProvider) 149 .apply(); 150 return null; 151 }); 152 } 153 } 154 155 @Override 156 public boolean isNewCallLogFragmentEnabled() { 157 return sharedPreferences.getBoolean(NEW_CALL_LOG_FRAGMENT_ENABLED_PREF_KEY, false); 158 } 159 160 @Override 161 public boolean isNewVoicemailFragmentEnabled() { 162 return sharedPreferences.getBoolean(NEW_VOICEMAIL_FRAGMENT_ENABLED_PREF_KEY, false); 163 } 164 165 @Override 166 public boolean isNewPeerEnabled() { 167 return sharedPreferences.getBoolean(NEW_PEER_ENABLED_PREF_KEY, false); 168 } 169 170 /** 171 * Returns true if the new call log framework is enabled, meaning that content observers are 172 * firing and PhoneLookupHistory is being populated, etc. 173 */ 174 @Override 175 public boolean isCallLogFrameworkEnabled() { 176 return sharedPreferences.getBoolean(NEW_CALL_LOG_FRAMEWORK_ENABLED_PREF_KEY, false); 177 } 178 179 @Override 180 public void schedulePollingJob() { 181 if (UserManagerCompat.isUserUnlocked(appContext)) { 182 JobScheduler jobScheduler = Assert.isNotNull(appContext.getSystemService(JobScheduler.class)); 183 @SuppressLint("MissingPermission") // Dialer has RECEIVE_BOOT permission 184 JobInfo jobInfo = 185 new JobInfo.Builder( 186 ScheduledJobIds.CALL_LOG_CONFIG_POLLING_JOB, 187 new ComponentName(appContext, PollingJob.class)) 188 .setPeriodic(TimeUnit.HOURS.toMillis(24)) 189 .setPersisted(true) 190 .setRequiresCharging(true) 191 .setRequiresDeviceIdle(true) 192 .build(); 193 LogUtil.i("CallLogConfigImpl.schedulePollingJob", "scheduling"); 194 jobScheduler.schedule(jobInfo); 195 } 196 } 197 198 /** 199 * Job which periodically force updates the {@link CallLogConfig}. This job is necessary to 200 * support {@link ConfigProvider ConfigProviders} which do not provide a reliable mechanism for 201 * listening to changes and calling {@link CallLogConfig#update()} directly, such as the {@link 202 * com.android.dialer.configprovider.SharedPrefConfigProvider}. 203 */ 204 public static final class PollingJob extends JobService { 205 206 @Override 207 public boolean onStartJob(JobParameters params) { 208 LogUtil.enterBlock("PollingJob.onStartJob"); 209 Futures.addCallback( 210 CallLogConfigComponent.get(getApplicationContext()).callLogConfig().update(), 211 new FutureCallback<Void>() { 212 @Override 213 public void onSuccess(Void unused) { 214 jobFinished(params, false /* needsReschedule */); 215 } 216 217 @Override 218 public void onFailure(Throwable throwable) { 219 ThreadUtil.getUiThreadHandler() 220 .post( 221 () -> { 222 throw new RuntimeException(throwable); 223 }); 224 jobFinished(params, false /* needsReschedule */); 225 } 226 }, 227 MoreExecutors.directExecutor()); 228 return true; 229 } 230 231 @Override 232 public boolean onStopJob(JobParameters params) { 233 return false; 234 } 235 } 236 } 237