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.net.ConnectivityManager; 21 import android.net.InetAddresses; 22 import android.net.LinkProperties; 23 import android.net.Network; 24 import android.net.NetworkCapabilities; 25 import android.net.NetworkRequest; 26 import android.os.ConditionVariable; 27 import android.os.Build.VERSION_CODES; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.support.annotation.CallSuper; 31 import android.telecom.PhoneAccountHandle; 32 import android.telephony.TelephonyManager; 33 import com.android.dialer.common.Assert; 34 import com.android.voicemail.impl.OmtpEvents; 35 import com.android.voicemail.impl.OmtpVvmCarrierConfigHelper; 36 import com.android.voicemail.impl.VoicemailStatus; 37 import com.android.voicemail.impl.VvmLog; 38 39 /** 40 * Base class for network request call backs for visual voicemail syncing with the Imap server. This 41 * handles retries and network requests. 42 */ 43 @TargetApi(VERSION_CODES.O) 44 public abstract class VvmNetworkRequestCallback extends ConnectivityManager.NetworkCallback { 45 46 private static final String TAG = "VvmNetworkRequest"; 47 48 // Timeout used to call ConnectivityManager.requestNetwork 49 private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000; 50 51 public static final String NETWORK_REQUEST_FAILED_TIMEOUT = "timeout"; 52 public static final String NETWORK_REQUEST_FAILED_LOST = "lost"; 53 54 protected Context context; 55 protected PhoneAccountHandle phoneAccount; 56 protected NetworkRequest networkRequest; 57 private ConnectivityManager connectivityManager; 58 private final OmtpVvmCarrierConfigHelper carrierConfigHelper; 59 private final VoicemailStatus.Editor status; 60 private boolean requestSent = false; 61 private boolean resultReceived = false; 62 private boolean released = false; 63 VvmNetworkRequestCallback( Context context, PhoneAccountHandle phoneAccount, VoicemailStatus.Editor status)64 public VvmNetworkRequestCallback( 65 Context context, PhoneAccountHandle phoneAccount, VoicemailStatus.Editor status) { 66 this.context = context; 67 this.phoneAccount = phoneAccount; 68 this.status = status; 69 carrierConfigHelper = new OmtpVvmCarrierConfigHelper(context, this.phoneAccount); 70 networkRequest = createNetworkRequest(); 71 } 72 VvmNetworkRequestCallback( OmtpVvmCarrierConfigHelper config, PhoneAccountHandle phoneAccount, VoicemailStatus.Editor status)73 public VvmNetworkRequestCallback( 74 OmtpVvmCarrierConfigHelper config, 75 PhoneAccountHandle phoneAccount, 76 VoicemailStatus.Editor status) { 77 context = config.getContext(); 78 this.phoneAccount = phoneAccount; 79 this.status = status; 80 carrierConfigHelper = config; 81 networkRequest = createNetworkRequest(); 82 } 83 getVoicemailStatusEditor()84 public VoicemailStatus.Editor getVoicemailStatusEditor() { 85 return status; 86 } 87 88 /** 89 * @return NetworkRequest for a proper transport type. Use only cellular network if the carrier 90 * requires it. Otherwise use whatever available. 91 */ createNetworkRequest()92 private NetworkRequest createNetworkRequest() { 93 94 NetworkRequest.Builder builder = 95 new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 96 97 TelephonyManager telephonyManager = 98 context.getSystemService(TelephonyManager.class).createForPhoneAccountHandle(phoneAccount); 99 // At this point mPhoneAccount should always be valid and telephonyManager will never be null 100 Assert.isNotNull(telephonyManager); 101 if (carrierConfigHelper.isCellularDataRequired()) { 102 VvmLog.d(TAG, "Transport type: CELLULAR"); 103 builder 104 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 105 .setNetworkSpecifier(telephonyManager.getNetworkSpecifier()); 106 } else { 107 VvmLog.d(TAG, "Transport type: ANY"); 108 } 109 return builder.build(); 110 } 111 getNetworkRequest()112 public NetworkRequest getNetworkRequest() { 113 return networkRequest; 114 } 115 116 @Override 117 @CallSuper onLost(Network network)118 public void onLost(Network network) { 119 VvmLog.i(TAG, "onLost"); 120 resultReceived = true; 121 onFailed(NETWORK_REQUEST_FAILED_LOST); 122 } 123 124 @Override 125 @CallSuper onAvailable(Network network)126 public void onAvailable(Network network) { 127 super.onAvailable(network); 128 resultReceived = true; 129 } 130 131 private static final int DEFAULT_IPV4_WAIT_DELAY_MS = 500; // in milliseconds 132 private final ConditionVariable mWaitV4Cv = new ConditionVariable(); 133 @Override 134 @CallSuper onLinkPropertiesChanged(Network network, LinkProperties lp)135 public void onLinkPropertiesChanged(Network network, LinkProperties lp) { 136 boolean hasIPv4 = (lp != null) && 137 (lp.isReachable(InetAddresses.parseNumericAddress("8.8.8.8"))); 138 if(hasIPv4) { 139 mWaitV4Cv.open(); 140 } 141 } waitForIpv4()142 public void waitForIpv4() { 143 VvmLog.w(TAG, "Waiting for IPV4 address..."); 144 mWaitV4Cv.block(DEFAULT_IPV4_WAIT_DELAY_MS); 145 } 146 147 @CallSuper onUnavailable()148 public void onUnavailable() { 149 VvmLog.i(TAG, "onUnavailable"); 150 resultReceived = true; 151 onFailed(NETWORK_REQUEST_FAILED_TIMEOUT); 152 } 153 requestNetwork()154 public void requestNetwork() { 155 if (requestSent == true) { 156 VvmLog.e(TAG, "requestNetwork() called twice"); 157 return; 158 } 159 requestSent = true; 160 getConnectivityManager().requestNetwork(getNetworkRequest(), this); 161 /** 162 * Somehow requestNetwork() with timeout doesn't work, and it's a hidden method. Implement our 163 * own timeout mechanism instead. 164 */ 165 Handler handler = new Handler(Looper.getMainLooper()); 166 handler.postDelayed( 167 new Runnable() { 168 @Override 169 public void run() { 170 if (resultReceived == false) { 171 onFailed(NETWORK_REQUEST_FAILED_TIMEOUT); 172 } 173 } 174 }, 175 NETWORK_REQUEST_TIMEOUT_MILLIS); 176 } 177 releaseNetwork()178 public void releaseNetwork() { 179 VvmLog.i(TAG, "releaseNetwork"); 180 if (!released) { 181 getConnectivityManager().unregisterNetworkCallback(this); 182 released = true; 183 } else { 184 VvmLog.w(TAG, "already released"); 185 } 186 } 187 getConnectivityManager()188 public ConnectivityManager getConnectivityManager() { 189 if (connectivityManager == null) { 190 connectivityManager = 191 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 192 } 193 return connectivityManager; 194 } 195 196 @CallSuper onFailed(String reason)197 public void onFailed(String reason) { 198 VvmLog.i(TAG, "onFailed: " + reason); 199 if (carrierConfigHelper.isCellularDataRequired()) { 200 carrierConfigHelper.handleEvent(status, OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED); 201 } else { 202 carrierConfigHelper.handleEvent(status, OmtpEvents.DATA_NO_CONNECTION); 203 } 204 releaseNetwork(); 205 } 206 } 207