001package com.gigya.android.sdk.network.adapter; 002 003import android.content.Context; 004import android.support.annotation.GuardedBy; 005import android.support.annotation.NonNull; 006import android.support.annotation.Nullable; 007 008import com.android.volley.AuthFailureError; 009import com.android.volley.DefaultRetryPolicy; 010import com.android.volley.NetworkResponse; 011import com.android.volley.ParseError; 012import com.android.volley.Request; 013import com.android.volley.RequestQueue; 014import com.android.volley.Response; 015import com.android.volley.VolleyError; 016import com.android.volley.VolleyLog; 017import com.android.volley.toolbox.HttpHeaderParser; 018import com.android.volley.toolbox.Volley; 019import com.gigya.android.sdk.GigyaLogger; 020import com.gigya.android.sdk.api.GigyaApiRequest; 021import com.gigya.android.sdk.api.GigyaApiHttpRequest; 022import com.gigya.android.sdk.api.IApiRequestFactory; 023import com.gigya.android.sdk.network.GigyaError; 024import com.gigya.android.sdk.utils.UrlUtils; 025 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.Map; 029import java.util.Queue; 030import java.util.concurrent.ConcurrentLinkedQueue; 031import java.util.concurrent.TimeUnit; 032 033public class VolleyNetworkProvider extends NetworkProvider { 034 035 private static final String LOG_TAG = "VolleyNetworkProvider"; 036 037 private RequestQueue _requestQueue; 038 private Queue<HttpVolleyTask> _blockedQueue = new ConcurrentLinkedQueue<>(); 039 040 VolleyNetworkProvider(IApiRequestFactory requestFactory, Context appContext) { 041 super(requestFactory); 042 _requestQueue = Volley.newRequestQueue(appContext); 043 // Enable Volley logs. 044 VolleyLog.DEBUG = GigyaLogger.isDebug(); 045 } 046 047 public static boolean isAvailable() { 048 try { 049 Class.forName("com.android.volley.toolbox.Volley"); 050 return true; 051 } catch (Exception ex) { 052 return false; 053 } 054 } 055 056 @Override 057 public void addToQueue(GigyaApiRequest request, IRestAdapterCallback networkCallbacks) { 058 _requestQueue.getCache().clear(); 059 060 if (_blocked) { 061 GigyaLogger.debug(LOG_TAG, "addToQueue: is blocked. adding to blocked queued - " + request.getApi()); 062 _blockedQueue.add(new HttpVolleyTask(request, networkCallbacks)); 063 return; 064 } 065 if (!_blockedQueue.isEmpty()) { 066 GigyaLogger.debug(LOG_TAG, "addToQueue: blockedQueue is empty releasing it - " + request.getApi()); 067 release(); 068 } 069 070 GigyaLogger.debug(LOG_TAG, "addToQueue: adding to queue - " + request.getApi()); 071 072 _requestFactory.sign(request); 073 VolleyNetworkRequest newRequest = createRequest(request, networkCallbacks); 074 _requestQueue.add(newRequest); 075 } 076 077 @Override 078 public void sendBlocking(GigyaApiRequest request, IRestAdapterCallback networkCallbacks) { 079 GigyaLogger.debug(LOG_TAG, "sendBlocking: " + request.getApi()); 080 _requestQueue.getCache().clear(); 081 082 _requestFactory.sign(request); 083 VolleyNetworkRequest newRequest = createRequest(request, networkCallbacks); 084 _requestQueue.add(newRequest); 085 _blocked = true; 086 } 087 088 @Override 089 public void release() { 090 super.release(); 091 if (_blockedQueue.isEmpty()) { 092 return; 093 } 094 095 // Traverse over blocked queue and release all. 096 while (!_blockedQueue.isEmpty()) { 097 098 final HttpVolleyTask task = _blockedQueue.poll(); 099 // Need to resign the request. 100 _requestFactory.sign(task.getRequest()); 101 102 final VolleyNetworkRequest queued = createRequest(task.getRequest(), task.getNetworkCallbacks()); 103 GigyaLogger.debug(LOG_TAG, "release: polled request - " + queued.getUrl()); 104 105 _requestQueue.add(queued); 106 } 107 } 108 109 @Override 110 public void cancel(String tag) { 111 if (tag == null) { 112 // Cancel all. 113 _requestQueue.cancelAll(new RequestQueue.RequestFilter() { 114 115 @Override 116 public boolean apply(Request<?> request) { 117 return true; 118 } 119 }); 120 _blockedQueue.clear(); 121 return; 122 } 123 _requestQueue.cancelAll(tag); 124 if (!_blockedQueue.isEmpty()) { 125 Iterator it = _blockedQueue.iterator(); 126 while (it.hasNext()) { 127 final HttpVolleyTask task = (HttpVolleyTask) it.next(); 128 final String requestTag = task.request.getTag(); 129 if (requestTag.equals(tag)) { 130 it.remove(); 131 } 132 } 133 } 134 } 135 136 //region VOLLEY SPECIFIC IMPLEMENTATION 137 138 private static class HttpVolleyTask { 139 140 private GigyaApiRequest request; 141 private final IRestAdapterCallback networkCallbacks; 142 143 private HttpVolleyTask(GigyaApiRequest request, IRestAdapterCallback networkCallbacks) { 144 this.request = request; 145 this.networkCallbacks = networkCallbacks; 146 } 147 148 public GigyaApiRequest getRequest() { 149 return request; 150 } 151 152 public void setRequest(GigyaApiRequest request) { 153 this.request = request; 154 } 155 156 public IRestAdapterCallback getNetworkCallbacks() { 157 return networkCallbacks; 158 } 159 } 160 161 /* 162 Generate a new Volley request. 163 */ 164 private VolleyNetworkRequest createRequest(final GigyaApiRequest request, final IRestAdapterCallback networkCallbacks) { 165 166 final GigyaApiHttpRequest signedRequest = _requestFactory.sign(request); 167 168 return new VolleyNetworkRequest( 169 request.getMethod().intValue(), 170 signedRequest.getUrl(), 171 new Response.Listener<VolleyResponsePair>() { 172 @Override 173 public void onResponse(VolleyResponsePair response) { 174 GigyaLogger.debug("GigyaApiResponse", "ApiService: " + signedRequest.getUrl() + "\n" + response); 175 if (networkCallbacks != null) { 176 networkCallbacks.onResponse(response.res, response.date); 177 } 178 } 179 }, 180 new Response.ErrorListener() { 181 @Override 182 public void onErrorResponse(VolleyError error) { 183 int errorCode = 0; 184 if (error.networkResponse != null) { 185 errorCode = error.networkResponse.statusCode; 186 } 187 final String localizedMessage = error.getLocalizedMessage() == null ? "" : error.getLocalizedMessage(); 188 final GigyaError gigyaError = new GigyaError(errorCode, localizedMessage, null); 189 GigyaLogger.debug("GigyaApiResponse", "GigyaApiResponse: Error " + 190 "ApiService: " + signedRequest.getUrl() + "\n" + 191 gigyaError.toString()); 192 if (networkCallbacks != null) { 193 networkCallbacks.onError(gigyaError); 194 } 195 } 196 }, 197 signedRequest.getEncodedParams(), 198 request.getTag() 199 ); 200 } 201 202 private static class VolleyNetworkRequest extends Request<VolleyResponsePair> { 203 204 /** 205 * Lock to guard mListener as it is cleared on cancel() and read on delivery. 206 */ 207 private final Object _lock = new Object(); 208 209 @Nullable 210 @GuardedBy("mLock") 211 private Response.Listener<VolleyResponsePair> _listener; 212 213 @Nullable 214 private String _body; 215 216 VolleyNetworkRequest(int method, 217 String url, 218 @NonNull Response.Listener<VolleyResponsePair> listener, 219 @NonNull Response.ErrorListener errorListener, 220 @Nullable String body, 221 String tag) { 222 super(method, url, errorListener); 223 setTag(tag); 224 _body = body; 225 _listener = listener; 226 setShouldCache(false); 227 setRetryPolicy(new DefaultRetryPolicy( 228 (int) TimeUnit.SECONDS.toMillis(30), //After the set time elapses the request will timeout 229 0, 230 DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); 231 } 232 233 @Override 234 public Map<String, String> getHeaders() { 235 Map<String, String> headers = new HashMap<>(); 236 headers.put("Accept-Encoding", "gzip, deflate"); 237 headers.put("connection", "close"); 238 return headers; 239 } 240 241 @Override 242 public byte[] getBody() throws AuthFailureError { 243 if (_body != null) { 244 return this._body.getBytes(); 245 } 246 return super.getBody(); 247 } 248 249 @Override 250 public void cancel() { 251 super.cancel(); 252 synchronized (_lock) { 253 _listener = null; 254 } 255 } 256 257 @Override 258 protected void deliverResponse(VolleyResponsePair response) { 259 Response.Listener<VolleyResponsePair> listener; 260 synchronized (_lock) { 261 listener = _listener; 262 } 263 if (listener != null) { 264 listener.onResponse(response); 265 } 266 } 267 268 @Override 269 protected Response<VolleyResponsePair> parseNetworkResponse(NetworkResponse response) { 270 String jsonString; 271 try { 272 final String dateHeader = response.headers.get("Date"); 273 final String encoding = response.headers.get("Content-Encoding"); 274 if (encoding != null && encoding.equals("gzip")) { 275 // Response contains GZIP encoding. 276 jsonString = UrlUtils.gzipDecode(response.data); 277 } else { 278 jsonString = new String( 279 response.data, 280 HttpHeaderParser.parseCharset(response.headers, "utf-8")); 281 } 282 return Response.success( 283 new VolleyResponsePair(jsonString, dateHeader), 284 HttpHeaderParser.parseCacheHeaders(response)); 285 } catch (Exception e) { 286 return Response.error(new ParseError(e)); 287 } 288 } 289 } 290 291 static class VolleyResponsePair { 292 293 final private String res; 294 final private String date; 295 296 VolleyResponsePair(String res, String date) { 297 this.res = res; 298 this.date = date; 299 } 300 } 301 302 //endregion 303 304}