· 4 years ago · Jun 06, 2021, 10:26 AM
1package cz.martykan.forecastie.tasks;
2
3import android.annotation.SuppressLint;
4import android.app.ProgressDialog;
5import android.content.Context;
6import android.content.SharedPreferences;
7import android.os.AsyncTask;
8import android.os.Build;
9import android.preference.PreferenceManager;
10import com.google.android.material.snackbar.Snackbar;
11import android.text.TextUtils;
12import android.util.Log;
13
14import java.io.BufferedReader;
15import java.io.Closeable;
16import java.io.IOException;
17import java.io.InputStreamReader;
18import java.io.UnsupportedEncodingException;
19import java.net.HttpURLConnection;
20import java.net.MalformedURLException;
21import java.net.URL;
22import java.net.URLEncoder;
23import java.util.concurrent.CountDownLatch;
24import java.util.concurrent.atomic.AtomicBoolean;
25
26import javax.net.ssl.HttpsURLConnection;
27import javax.net.ssl.SSLContext;
28import javax.net.ssl.SSLSocketFactory;
29
30import cz.martykan.forecastie.Constants;
31import cz.martykan.forecastie.R;
32import cz.martykan.forecastie.activities.MainActivity;
33import cz.martykan.forecastie.utils.Language;
34import cz.martykan.forecastie.utils.certificate.CertificateUtils;
35
36public abstract class GenericRequestTask extends AsyncTask<String, String, TaskOutput> {
37
38 ProgressDialog progressDialog;
39 protected Context context;
40 protected MainActivity activity;
41 public int loading = 0;
42
43 private static CountDownLatch certificateCountDownLatch = new CountDownLatch(0);
44 private static boolean certificateTried = false;
45 private static boolean certificateFetchTried = false;
46 private static SSLContext sslContext;
47
48 public GenericRequestTask(Context context, MainActivity activity, ProgressDialog progressDialog) {
49 this.context = context;
50 this.activity = activity;
51 this.progressDialog = progressDialog;
52 }
53
54 @Override
55 protected void onPreExecute() {
56 incLoadingCounter();
57 if (!progressDialog.isShowing()) {
58 progressDialog.setMessage(context.getString(R.string.downloading_data));
59 progressDialog.setCanceledOnTouchOutside(false);
60 progressDialog.show();
61 }
62 }
63
64 @Override
65 protected TaskOutput doInBackground(String... params) {
66 TaskOutput output = new TaskOutput();
67
68 String response = "";
69 String[] reqParams = new String[]{};
70
71 if (params != null && params.length > 0) {
72 final String zeroParam = params[0];
73 if ("cachedResponse".equals(zeroParam)) {
74 response = params[1];
75 output.taskResult = TaskResult.SUCCESS;
76 } else if ("coords".equals(zeroParam)) {
77 String lat = params[1];
78 String lon = params[2];
79 reqParams = new String[]{"coords", lat, lon};
80 } else if ("city".equals(zeroParam)) {
81 reqParams = new String[]{"city", params[1]};
82 }
83 }
84
85 if (response.isEmpty()) {
86 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
87 response = makeRequest(output, response, reqParams);
88 } else {
89 response = makeRequestWithCheckForCertificate(output, response, reqParams);
90 }
91 }
92
93 if (TaskResult.SUCCESS.equals(output.taskResult)) {
94 ParseResult parseResult = parseResponse(response);
95 if (ParseResult.CITY_NOT_FOUND.equals(parseResult)) {
96 restorePreviousCity();
97 }
98 output.parseResult = parseResult;
99 }
100
101 return output;
102 }
103
104 private String makeRequest(TaskOutput output, String response, String[] reqParams) {
105 try {
106 URL url = provideURL(reqParams);
107 Log.i("URL", url.toString());
108 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
109 if (urlConnection instanceof HttpsURLConnection) {
110 try {
111 certificateCountDownLatch.await();
112 if (sslContext != null) {
113 SSLSocketFactory socketFactory = sslContext.getSocketFactory();
114 ((HttpsURLConnection) urlConnection).setSSLSocketFactory(socketFactory);
115 }
116 certificateCountDownLatch.countDown();
117 } catch (InterruptedException e) {
118 e.printStackTrace();
119 }
120 }
121 if (urlConnection.getResponseCode() == 200) {
122 InputStreamReader inputStreamReader = new InputStreamReader(urlConnection.getInputStream());
123 BufferedReader r = new BufferedReader(inputStreamReader);
124
125 StringBuilder stringBuilder = new StringBuilder();
126 String line;
127 while ((line = r.readLine()) != null) {
128 stringBuilder.append(line);
129 stringBuilder.append("\n");
130 }
131 response += stringBuilder.toString();
132 close(r);
133 urlConnection.disconnect();
134 Log.i("Task", "done successfully");
135 output.taskResult = TaskResult.SUCCESS;
136 MainActivity.saveLastUpdateTime(PreferenceManager.getDefaultSharedPreferences(context));
137 } else if (urlConnection.getResponseCode() == 401) {
138 Log.w("Task", "invalid API key");
139 output.taskResult = TaskResult.INVALID_API_KEY;
140 } else if (urlConnection.getResponseCode() == 429) {
141 Log.w("Task", "too many requests");
142 output.taskResult = TaskResult.TOO_MANY_REQUESTS;
143 } else {
144 Log.w("Task", "http error " + urlConnection.getResponseCode());
145 output.taskResult = TaskResult.HTTP_ERROR;
146 }
147 } catch (IOException e) {
148 e.printStackTrace();
149 output.taskResult = TaskResult.IO_EXCEPTION;
150 output.taskError = e;
151 }
152
153 return response;
154 }
155
156 private String makeRequestWithCheckForCertificate(TaskOutput output, String response, String[] reqParams) {
157 boolean tryAgain = false;
158 do {
159 response = makeRequest(output, response, reqParams);
160 if (output.taskResult == TaskResult.IO_EXCEPTION && output.taskError instanceof IOException) {
161 if (CertificateUtils.isCertificateException((IOException) output.taskError)) {
162 Log.e("Invalid Certificate", output.taskError.getMessage());
163 try {
164 certificateCountDownLatch.await();
165 tryAgain = !certificateTried || !certificateFetchTried;
166 if (tryAgain) {
167 AtomicBoolean doNotRetry = new AtomicBoolean(false);
168 sslContext = CertificateUtils.addCertificate(context, doNotRetry,
169 certificateTried);
170 certificateTried = true;
171 if (!certificateFetchTried) {
172 certificateFetchTried = doNotRetry.get();
173 }
174 tryAgain = sslContext != null;
175 }
176 certificateCountDownLatch.countDown();
177 } catch (InterruptedException ex) {
178 Log.e("Invalid Certificate", "error");
179 ex.printStackTrace();
180 }
181 } else {
182 Log.e("IOException Data", response);
183 tryAgain = false;
184 }
185 } else {
186 tryAgain = false;
187 }
188 } while (tryAgain);
189 return response;
190 }
191
192 @Override
193 protected void onPostExecute(TaskOutput output) {
194 if (loading == 1) {
195 progressDialog.dismiss();
196 }
197 decLoadingCounter();
198
199 updateMainUI();
200
201 handleTaskOutput(output);
202 }
203
204 protected final void handleTaskOutput(TaskOutput output) {
205 switch (output.taskResult) {
206 case SUCCESS:
207 ParseResult parseResult = output.parseResult;
208 if (ParseResult.CITY_NOT_FOUND.equals(parseResult)) {
209 Snackbar.make(activity.findViewById(android.R.id.content), context.getString(R.string.msg_city_not_found), Snackbar.LENGTH_LONG).show();
210 } else if (ParseResult.JSON_EXCEPTION.equals(parseResult)) {
211 Snackbar.make(activity.findViewById(android.R.id.content), context.getString(R.string.msg_err_parsing_json), Snackbar.LENGTH_LONG).show();
212 }
213 break;
214 case TOO_MANY_REQUESTS:
215 Snackbar.make(activity.findViewById(android.R.id.content), context.getString(R.string.msg_too_many_requests), Snackbar.LENGTH_LONG).show();
216 break;
217 case INVALID_API_KEY:
218 Snackbar.make(activity.findViewById(android.R.id.content), context.getString(R.string.msg_invalid_api_key), Snackbar.LENGTH_LONG).show();
219 break;
220 case HTTP_ERROR:
221 Snackbar.make(activity.findViewById(android.R.id.content), context.getString(R.string.msg_http_error), Snackbar.LENGTH_LONG).show();
222 break;
223 case IO_EXCEPTION:
224 Snackbar.make(activity.findViewById(android.R.id.content), context.getString(R.string.msg_connection_not_available), Snackbar.LENGTH_LONG).show();
225 break;
226 }
227 }
228
229 private URL provideURL(String[] reqParams) throws UnsupportedEncodingException, MalformedURLException {
230 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
231 String apiKey = sp.getString("apiKey", context.getString(R.string.apiKey));
232
233 StringBuilder urlBuilder = new StringBuilder("https://api.openweathermap.org/data/2.5/");
234 urlBuilder.append(getAPIName()).append("?");
235 if (reqParams.length > 0) {
236 final String zeroParam = reqParams[0];
237 if ("coords".equals(zeroParam)) {
238 urlBuilder.append("lat=").append(reqParams[1]).append("&lon=").append(reqParams[2]);
239 } else if ("city".equals(zeroParam)) {
240 urlBuilder.append("q=").append(reqParams[1]);
241 }
242 } else {
243 final String cityId = sp.getString("cityId", Constants.DEFAULT_CITY_ID);
244 urlBuilder.append("id=").append(URLEncoder.encode(cityId, "UTF-8"));
245 }
246 urlBuilder.append("&lang=").append(Language.getOwmLanguage());
247 urlBuilder.append("&mode=json");
248 urlBuilder.append("&appid=").append(apiKey);
249
250 return new URL(urlBuilder.toString());
251 }
252
253 @SuppressLint("ApplySharedPref")
254 private void restorePreviousCity() {
255 if (!TextUtils.isEmpty(activity.recentCityId)) {
256 PreferenceManager.getDefaultSharedPreferences(context).edit()
257 .putString("cityId", activity.recentCityId)
258 .commit();
259 activity.recentCityId = "";
260 }
261 }
262
263 private static void close(Closeable x) {
264 try {
265 if (x != null) {
266 x.close();
267 }
268 } catch (IOException e) {
269 Log.e("IOException Data", "Error");
270 }
271 }
272
273 private void incLoadingCounter() {
274 loading++;
275 }
276
277 private void decLoadingCounter() {
278 loading--;
279 }
280
281 protected void updateMainUI() {
282 }
283
284 protected abstract ParseResult parseResponse(String response);
285
286 protected abstract String getAPIName();
287}
288