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