/* * Copyright 2018-2019 Prebid.org, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.prebid.mobile; import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.Build; import android.os.CountDownTimer; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; class PrebidServerAdapter implements DemandAdapter { private ArrayList<ServerConnector> serverConnectors; PrebidServerAdapter() { serverConnectors = new ArrayList<>(); } @Override public void requestDemand(RequestParams params, DemandAdapterListener listener, String auctionId) { ServerConnector connector = new ServerConnector(this, listener, params, auctionId); serverConnectors.add(connector); connector.execute(); } @Override public void stopRequest(String auctionId) { ArrayList<ServerConnector> toRemove = new ArrayList<>(); for (ServerConnector connector : serverConnectors) { if (connector.getAuctionId().equals(auctionId)) { connector.destroy(); toRemove.add(connector); } } serverConnectors.removeAll(toRemove); } static class ServerConnector extends AsyncTask<Object, Object, ServerConnector.AsyncTaskResult<JSONObject>> { private static final int TIMEOUT_COUNT_DOWN_INTERVAL = 500; private final WeakReference<PrebidServerAdapter> prebidServerAdapter; private final TimeoutCountDownTimer timeoutCountDownTimer; private final RequestParams requestParams; private final String auctionId; private DemandAdapterListener listener; private boolean timeoutFired; private final AdType adType; ServerConnector(PrebidServerAdapter prebidServerAdapter, DemandAdapterListener listener, RequestParams requestParams, String auctionId) { this.prebidServerAdapter = new WeakReference<>(prebidServerAdapter); this.listener = listener; this.requestParams = requestParams; this.auctionId = auctionId; timeoutCountDownTimer = new TimeoutCountDownTimer(PrebidMobile.getTimeoutMillis(), TIMEOUT_COUNT_DOWN_INTERVAL); adType = requestParams.getAdType(); } @Override protected void onPreExecute() { super.onPreExecute(); timeoutCountDownTimer.start(); } @Override @WorkerThread protected AsyncTaskResult<JSONObject> doInBackground(Object... objects) { try { long demandFetchStartTime = System.currentTimeMillis(); BidLog.BidLogEntry entry = new BidLog.BidLogEntry(); URL url = new URL(getHost()); entry.setRequestUrl(getHost()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Accept", "application/json"); if(canIAccessDeviceData()) { String existingCookie = getExistingCookie(); if (existingCookie != null) { conn.setRequestProperty(PrebidServerSettings.COOKIE_HEADER, existingCookie); } // todo still pass cookie if limit ad tracking? } conn.setRequestMethod("POST"); conn.setConnectTimeout(PrebidMobile.getTimeoutMillis()); // Add post data OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream(), "UTF-8"); JSONObject postData = getPostData(); String postString = postData.toString(); LogUtil.d("Sending request for auction " + auctionId + " with post data: " + postString); wr.write(postString); wr.flush(); entry.setRequestBody(postString); // Start the connection conn.connect(); // Read request response int httpResult = conn.getResponseCode(); long demandFetchEndTime = System.currentTimeMillis(); entry.setResponseCode(httpResult); if (httpResult == HttpURLConnection.HTTP_OK) { StringBuilder builder = new StringBuilder(); InputStream is = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "utf-8")); String line; while ((line = reader.readLine()) != null) { builder.append(line); } reader.close(); is.close(); String result = builder.toString(); entry.setResponse(result); JSONObject response = new JSONObject(result); httpCookieSync(conn.getHeaderFields()); // in the future, this can be improved to parse response base on request versions if (!PrebidMobile.timeoutMillisUpdated) { int tmaxRequest = -1; try { tmaxRequest = response.getJSONObject("ext").getInt("tmaxrequest"); } catch (JSONException e) { // ignore this } if (tmaxRequest >= 0) { PrebidMobile.setTimeoutMillis(Math.min((int) (demandFetchEndTime - demandFetchStartTime) + tmaxRequest + 200, 2000)); // adding 200ms as safe time PrebidMobile.timeoutMillisUpdated = true; } } BidLog.getInstance().setLastEntry(entry); return new AsyncTaskResult<>(response); } else if (httpResult >= HttpURLConnection.HTTP_BAD_REQUEST) { StringBuilder builder = new StringBuilder(); InputStream is = conn.getErrorStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "utf-8")); String line; while ((line = reader.readLine()) != null) { builder.append(line); } reader.close(); is.close(); String result = builder.toString(); entry.setResponse(result); LogUtil.d("Getting response for auction " + getAuctionId() + ": " + result); Pattern storedRequestNotFound = Pattern.compile("^Invalid request: Stored Request with ID=\".*\" not found."); Pattern storedImpNotFound = Pattern.compile("^Invalid request: Stored Imp with ID=\".*\" not found."); Pattern invalidBannerSize = Pattern.compile("^Invalid request: Request imp\\[\\d\\].banner.format\\[\\d\\] must define non-zero \"h\" and \"w\" properties."); Pattern invalidInterstitialSize = Pattern.compile("Invalid request: Unable to set interstitial size list"); Matcher m = storedRequestNotFound.matcher(result); Matcher m2 = invalidBannerSize.matcher(result); Matcher m3 = storedImpNotFound.matcher(result); Matcher m4 = invalidInterstitialSize.matcher(result); BidLog.getInstance().setLastEntry(entry); if (m.find() || result.contains("No stored request")) { return new AsyncTaskResult<>(ResultCode.INVALID_ACCOUNT_ID); } else if (m3.find() || result.contains("No stored imp")) { return new AsyncTaskResult<>(ResultCode.INVALID_CONFIG_ID); } else if (m2.find() || m4.find() || result.contains("Request imp[0].banner.format")) { return new AsyncTaskResult<>(ResultCode.INVALID_SIZE); } else { return new AsyncTaskResult<>(ResultCode.PREBID_SERVER_ERROR); } } } catch (MalformedURLException e) { return new AsyncTaskResult<>(e); } catch (UnsupportedEncodingException e) { return new AsyncTaskResult<>(e); } catch (SocketTimeoutException ex) { return new AsyncTaskResult<>(ResultCode.TIMEOUT); } catch (IOException e) { return new AsyncTaskResult<>(e); } catch (JSONException e) { return new AsyncTaskResult<>(e); } catch (NoContextException ex) { return new AsyncTaskResult<>(ResultCode.INVALID_CONTEXT); } catch (Exception e) { return new AsyncTaskResult<>(e); } return new AsyncTaskResult<>(new RuntimeException("ServerConnector exception")); } @Override @MainThread protected void onPostExecute(AsyncTaskResult<JSONObject> asyncTaskResult) { super.onPostExecute(asyncTaskResult); timeoutCountDownTimer.cancel(); if (asyncTaskResult.getError() != null) { asyncTaskResult.getError().printStackTrace(); //Default error notifyDemandFailed(ResultCode.PREBID_SERVER_ERROR); removeThisTask(); return; } else if (asyncTaskResult.getResultCode() != null) { notifyDemandFailed(asyncTaskResult.getResultCode()); removeThisTask(); return; } JSONObject jsonObject = asyncTaskResult.getResult(); HashMap<String, String> keywords = new HashMap<>(); boolean containTopBid = false; if (jsonObject != null) { LogUtil.d("Getting response for auction " + getAuctionId() + ": " + jsonObject.toString()); try { JSONArray seatbid = jsonObject.getJSONArray("seatbid"); if (seatbid != null) { for (int i = 0; i < seatbid.length(); i++) { JSONObject seat = seatbid.getJSONObject(i); JSONArray bids = seat.getJSONArray("bid"); if (bids != null) { for (int j = 0; j < bids.length(); j++) { JSONObject bid = bids.getJSONObject(j); JSONObject hb_key_values = null; try { hb_key_values = bid.getJSONObject("ext").getJSONObject("prebid").getJSONObject("targeting"); } catch (JSONException e) { // this can happen if lower bids exist on the same seat } if (hb_key_values != null) { Iterator it = hb_key_values.keys(); boolean containBids = false; while (it.hasNext()) { String key = (String) it.next(); if (key.equals("hb_cache_id")) { containTopBid = true; } if (key.startsWith("hb_cache_id")) { containBids = true; } } it = hb_key_values.keys(); if (containBids) { while (it.hasNext()) { String key = (String) it.next(); keywords.put(key, hb_key_values.getString(key)); } } } } } } } } catch (JSONException e) { LogUtil.e("Error processing JSON response."); } } if (!keywords.isEmpty() && containTopBid) { notifyContainsTopBid(true); notifyDemandReady(keywords); } else { notifyContainsTopBid(false); notifyDemandFailed(ResultCode.NO_BIDS); } removeThisTask(); } @Override @MainThread protected void onCancelled() { super.onCancelled(); if (timeoutFired) { notifyDemandFailed(ResultCode.TIMEOUT); } else { timeoutCountDownTimer.cancel(); } removeThisTask(); } private void removeThisTask() { @Nullable PrebidServerAdapter prebidServerAdapter = this.prebidServerAdapter.get(); if (prebidServerAdapter == null) { return; } prebidServerAdapter.serverConnectors.remove(this); } String getAuctionId() { return auctionId; } void destroy() { this.cancel(true); this.listener = null; } @MainThread void notifyDemandReady(HashMap<String, String> keywords) { if (this.listener == null) { return; } listener.onDemandReady(keywords, getAuctionId()); } @MainThread void notifyDemandFailed(ResultCode code) { if (this.listener == null) { return; } listener.onDemandFailed(code, getAuctionId()); } private void notifyContainsTopBid(boolean contains) { BidLog.BidLogEntry entry = BidLog.getInstance().getLastBid(); if (entry != null) { entry.setContainsTopBid(contains); } } private String getHost() { return PrebidMobile.getPrebidServerHost().getHostUrl(); } /** * Synchronize the uuid2 cookie to the Webview Cookie Jar * This is only done if there is no present cookie. * * @param headers headers to extract cookies from for syncing */ @SuppressWarnings("deprecation") private void httpCookieSync(Map<String, List<String>> headers) { if (headers == null || headers.isEmpty()) return; CookieManager cm = CookieManager.getInstance(); if (cm == null) { LogUtil.i("PrebidNewAPI", "Unable to find a CookieManager"); return; } try { String existingUUID = getExistingCookie(); for (Map.Entry<String, List<String>> entry : headers.entrySet()) { String key = entry.getKey(); // Only "Set-cookie" and "Set-cookie2" pair will be parsed if (key != null && (key.equalsIgnoreCase(PrebidServerSettings.VERSION_ZERO_HEADER) || key.equalsIgnoreCase(PrebidServerSettings.VERSION_ONE_HEADER))) { for (String cookieStr : entry.getValue()) { if (!TextUtils.isEmpty(cookieStr) && cookieStr.contains(PrebidServerSettings.AN_UUID)) { // pass uuid2 to WebView Cookie jar if it's empty or outdated if (existingUUID == null || !cookieStr.contains(existingUUID)) { cm.setCookie(PrebidServerSettings.COOKIE_DOMAIN, cookieStr); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // CookieSyncManager is deprecated in API 21 Lollipop CookieSyncManager.createInstance(PrebidMobile.getApplicationContext()); CookieSyncManager csm = CookieSyncManager.getInstance(); if (csm == null) { LogUtil.i("Unable to find a CookieSyncManager"); return; } csm.sync(); } else { cm.flush(); } } } } } } } catch (IllegalStateException ise) { } catch (Exception e) { } } private String getExistingCookie() { try { CookieSyncManager.createInstance(PrebidMobile.getApplicationContext()); CookieManager cm = CookieManager.getInstance(); if (cm != null) { String wvcookie = cm.getCookie(PrebidServerSettings.COOKIE_DOMAIN); if (!TextUtils.isEmpty(wvcookie)) { String[] existingCookies = wvcookie.split("; "); for (String cookie : existingCookies) { if (cookie != null && cookie.contains(PrebidServerSettings.AN_UUID)) { return cookie; } } } } } catch (Exception e) { } return null; } private JSONObject getPostData() throws NoContextException { JSONObject postData = new JSONObject(); try { String id = UUID.randomUUID().toString(); postData.put("id", id); JSONObject source = new JSONObject(); source.put("tid", id); postData.put("source", source); // add ad units JSONArray imp = getImp(); if (imp != null && imp.length() > 0) { postData.put("imp", imp); } // add device JSONObject device = getDeviceObject(); if (device != null && device.length() > 0) { postData.put(PrebidServerSettings.REQUEST_DEVICE, device); } // add app JSONObject app = getAppObject(); if (device != null && device.length() > 0) { postData.put(PrebidServerSettings.REQUEST_APP, app); } // add user JSONObject user = getUserObject(); if (user != null && user.length() > 0) { postData.put(PrebidServerSettings.REQUEST_USER, user); } // add regs JSONObject regs = getRegsObject(); if (regs != null && regs.length() > 0) { postData.put("regs", regs); } // add targeting keywords request JSONObject ext = getRequestExtData(); if (ext != null && ext.length() > 0) { postData.put("ext", ext); } JSONObject objectWithoutEmptyValues = Util.getObjectWithoutEmptyValues(postData); if (objectWithoutEmptyValues != null) { postData = objectWithoutEmptyValues; JSONObject prebid = postData.getJSONObject("ext").getJSONObject("prebid"); JSONObject cache = new JSONObject(); JSONObject bids = new JSONObject(); cache.put("bids", bids); if (adType.equals(AdType.VIDEO) || adType.equals(AdType.VIDEO_INTERSTITIAL) || adType.equals(AdType.REWARDED_VIDEO)) { cache.put("vastxml", bids); } prebid.put("cache", cache); JSONObject targetingEmpty = new JSONObject(); prebid.put("targeting", targetingEmpty); } } catch (JSONException e) { } return postData; } private JSONObject getRequestExtData() { JSONObject ext = new JSONObject(); JSONObject prebid = new JSONObject(); try { JSONObject storedRequest = new JSONObject(); storedRequest.put("id", PrebidMobile.getPrebidServerAccountId()); prebid.put("storedrequest", storedRequest); JSONObject data = new JSONObject().put("bidders", new JSONArray(TargetingParams.getAccessControlList())); prebid.put("data", data); ext.put("prebid", prebid); } catch (JSONException e) { e.printStackTrace(); } return ext; } private JSONArray getImp() throws NoContextException { JSONArray impConfigs = new JSONArray(); // takes information from the ad units // look up the configuration of the ad unit try { JSONObject imp = new JSONObject(); JSONObject ext = new JSONObject(); imp.put("id", "PrebidMobile"); imp.put("secure", 1); if (adType.equals(AdType.INTERSTITIAL) || adType.equals(AdType.VIDEO_INTERSTITIAL) || adType.equals(AdType.REWARDED_VIDEO)) { imp.put("instl", 1); } if (adType.equals(AdType.INTERSTITIAL)) { JSONObject banner = new JSONObject(); JSONArray format = new JSONArray(); Context context = PrebidMobile.getApplicationContext(); if (context != null) { format.put(new JSONObject().put("w", context.getResources().getConfiguration().screenWidthDp).put("h", context.getResources().getConfiguration().screenHeightDp)); } else { // Unlikely this is being called, if so, please check if you've set up the SDK properly throw new NoContextException(); } banner.put("format", format); imp.put("banner", banner); } else if (adType.equals(AdType.BANNER)) { JSONObject banner = new JSONObject(); JSONArray format = new JSONArray(); for (AdSize size : requestParams.getAdSizes()) { format.put(new JSONObject().put("w", size.getWidth()).put("h", size.getHeight())); } banner.put("format", format); imp.put("banner", banner); } else if (adType.equals(AdType.NATIVE)) { // add native request JSONObject nativeObj = new JSONObject(); JSONObject request = new JSONObject(); JSONArray assets = new JSONArray(); NativeRequestParams params = requestParams.getNativeRequestParams(); if (params.getContextType() != null) { request.put(NativeRequestParams.CONTEXT, params.getContextType().getID()); } if (params.getContextsubtype() != null) { request.put(NativeRequestParams.CONTEXT_SUB_TYPE, params.getContextsubtype().getID()); } if (params.getPlacementType() != null) { request.put(NativeRequestParams.PLACEMENT_TYPE, params.getPlacementType().getID()); } request.put(NativeRequestParams.PLACEMENT_COUNT, params.getPlacementCount()); request.put(NativeRequestParams.SEQ, params.getSeq()); request.put(NativeRequestParams.A_URL_SUPPORT, params.isAUrlSupport() ? 1 : 0); request.put(NativeRequestParams.D_URL_SUPPORT, params.isDUrlSupport() ? 1 : 0); if (!params.getEventTrackers().isEmpty()) { JSONArray trackers = new JSONArray(); for (NativeEventTracker tracker : params.getEventTrackers()) { JSONObject trackerObject = new JSONObject(); trackerObject.put(NativeRequestParams.EVENT, tracker.getEvent().getID()); JSONArray methodsArray = new JSONArray(); for (NativeEventTracker.EVENT_TRACKING_METHOD method : tracker.getMethods()) { methodsArray.put(method.getID()); } trackerObject.put(NativeRequestParams.METHODS, methodsArray); trackerObject.put(NativeRequestParams.EXT, tracker.getExtObject()); trackers.put(trackerObject); } request.put(NativeRequestParams.EVENT_TRACKERS, trackers); } request.put(NativeRequestParams.PRIVACY, params.isPrivacy() ? 1 : 0); request.put(NativeRequestParams.EXT, params.getExt()); if (!params.getAssets().isEmpty()) { for (NativeAsset asset : params.getAssets()) { JSONObject assetObj; switch (asset.getType()) { case TITLE: NativeTitleAsset titleAsset = (NativeTitleAsset) asset; assetObj = new JSONObject(); JSONObject title = new JSONObject(); title.put(NativeRequestParams.LENGTH, titleAsset.getLen()); if (titleAsset.getTitleExt() != null) { title.put(NativeRequestParams.EXT, titleAsset.getTitleExt()); } assetObj.put(NativeRequestParams.TITLE, title); assetObj.put(NativeRequestParams.REQUIRED, titleAsset.isRequired() ? 1 : 0); assetObj.put(NativeRequestParams.EXT, titleAsset.getAssetExt()); assets.put(assetObj); break; case IMAGE: NativeImageAsset imageAsset = (NativeImageAsset) asset; assetObj = new JSONObject(); JSONObject image = new JSONObject(); image.put(NativeRequestParams.TYPE, imageAsset.getImageType().getID()); if (imageAsset.getImageExt() != null) { image.put(NativeRequestParams.EXT, imageAsset.getImageExt()); } if (imageAsset.getHMin() > 0 && imageAsset.getWMin() > 0) { image.put(NativeRequestParams.WIDTH_MIN, imageAsset.getWMin()); image.put(NativeRequestParams.HEIGHT_MIN, imageAsset.getHMin()); } if (imageAsset.getH() > 0 && imageAsset.getW() > 0) { image.put(NativeRequestParams.WIDTH, imageAsset.getW()); image.put(NativeRequestParams.HEIGHT, imageAsset.getH()); } if (!imageAsset.getMimes().isEmpty()) { JSONArray imageMimesArray = new JSONArray(); for (String mime : imageAsset.getMimes()) { imageMimesArray.put(mime); } image.put(NativeRequestParams.MIMES, imageMimesArray); } assetObj.put(NativeRequestParams.IMAGE, image); assetObj.put(NativeRequestParams.REQUIRED, imageAsset.isRequired() ? 1 : 0); assetObj.put(NativeRequestParams.EXT, imageAsset.getAssetExt()); assets.put(assetObj); break; case DATA: NativeDataAsset dataAsset = (NativeDataAsset) asset; assetObj = new JSONObject(); JSONObject data = new JSONObject(); data.put(NativeRequestParams.TYPE, dataAsset.getDataType().getID()); if (dataAsset.getLen() > 0) { data.put(NativeRequestParams.LENGTH, dataAsset.getLen()); } if (dataAsset.getDataExt() != null) { data.put(NativeRequestParams.EXT, dataAsset.getDataExt()); } assetObj.put(NativeRequestParams.DATA, data); assetObj.put(NativeRequestParams.REQUIRED, dataAsset.isRequired() ? 1 : 0); assetObj.put(NativeRequestParams.EXT, dataAsset.getAssetExt()); assets.put(assetObj); break; } } } request.put(NativeRequestParams.ASSETS, assets); request.put(NativeRequestParams.VERSION, NativeRequestParams.SUPPORTED_VERSION); nativeObj.put(NativeRequestParams.REQUEST, request.toString()); nativeObj.put(NativeRequestParams.VERSION, NativeRequestParams.SUPPORTED_VERSION); imp.put(NativeRequestParams.NATIVE, nativeObj); } else if (adType.equals(AdType.VIDEO) || adType.equals(AdType.VIDEO_INTERSTITIAL) || adType.equals(AdType.REWARDED_VIDEO)) { JSONObject video = new JSONObject(); Integer placementValue = null; VideoBaseAdUnit.Parameters parameters = requestParams.getVideoParameters(); if (parameters != null) { List<Integer> apiList = Util.convertCollection(parameters.getApi(), new Util.Function1<Integer, Signals.Api>() { @Override public Integer apply(Signals.Api element) { return element.value; } }); List<Integer> playbackMethodList = Util.convertCollection(parameters.getPlaybackMethod(), new Util.Function1<Integer, Signals.PlaybackMethod>() { @Override public Integer apply(Signals.PlaybackMethod element) { return element.value; } }); List<Integer> protocolList = Util.convertCollection(parameters.getProtocols(), new Util.Function1<Integer, Signals.Protocols>() { @Override public Integer apply(Signals.Protocols element) { return element.value; } }); Integer startDelayValue = null; Signals.StartDelay startDelay = parameters.getStartDelay(); if (startDelay != null) { startDelayValue = startDelay.value; } Signals.Placement placement = parameters.getPlacement(); if (placement != null) { placementValue = placement.value; } video.put("api", new JSONArray(apiList)); video.put("maxbitrate", parameters.getMaxBitrate()); video.put("minbitrate", parameters.getMinBitrate()); video.put("maxduration", parameters.getMaxDuration()); video.put("minduration", parameters.getMinDuration()); video.put("mimes", new JSONArray(parameters.getMimes())); video.put("playbackmethod", new JSONArray(playbackMethodList)); video.put("protocols", new JSONArray(protocolList)); video.put("startdelay", startDelayValue); } Integer placementValueDefault = null; if (adType.equals(AdType.VIDEO)) { for (AdSize size : requestParams.getAdSizes()) { video.put("w", size.getWidth()); video.put("h", size.getHeight()); } } else if (adType.equals(AdType.VIDEO_INTERSTITIAL) || adType.equals(AdType.REWARDED_VIDEO)) { Context context = PrebidMobile.getApplicationContext(); if (context != null) { video.put("w", context.getResources().getConfiguration().screenWidthDp); video.put("h", context.getResources().getConfiguration().screenHeightDp); } placementValueDefault = 5; } if (placementValue == null) { placementValue = placementValueDefault; } video.put("placement", placementValue); video.put("linearity", 1); imp.put("video", video); } JSONObject prebid = new JSONObject(); ext.put("prebid", prebid); JSONObject context = new JSONObject(); context.put("data", Util.toJson(requestParams.getContextDataDictionary())); context.put("keywords", TextUtils.join(",", requestParams.getContextKeywordsSet())); ext.put("context", context); JSONObject storedrequest = new JSONObject(); prebid.put("storedrequest", storedrequest); storedrequest.put("id", requestParams.getConfigId()); if (!TextUtils.isEmpty(PrebidMobile.getStoredAuctionResponse())) { JSONObject storedAuctionResponse = new JSONObject(); prebid.put("storedauctionresponse", storedAuctionResponse); storedAuctionResponse.put("id", PrebidMobile.getStoredAuctionResponse()); } if (!PrebidMobile.getStoredBidResponses().isEmpty()) { JSONArray bidResponseArray = new JSONArray(); prebid.put("storedbidresponse", bidResponseArray); for (String bidder : PrebidMobile.getStoredBidResponses().keySet()) { String bidId = PrebidMobile.getStoredBidResponses().get(bidder); if (!TextUtils.isEmpty(bidder) && !TextUtils.isEmpty(bidId)) { JSONObject storedBid = new JSONObject(); storedBid.put("bidder", bidder); storedBid.put("id", bidId); bidResponseArray.put(storedBid); } } } if (adType.equals(AdType.REWARDED_VIDEO)) { prebid.put("is_rewarded_inventory", 1); } imp.put("ext", ext); impConfigs.put(imp); } catch (JSONException e) { } return impConfigs; } private JSONObject getDeviceObject() { JSONObject device = new JSONObject(); try { // Device make if (!TextUtils.isEmpty(PrebidServerSettings.deviceMake)) device.put(PrebidServerSettings.REQUEST_DEVICE_MAKE, PrebidServerSettings.deviceMake); // Device model if (!TextUtils.isEmpty(PrebidServerSettings.deviceModel)) device.put(PrebidServerSettings.REQUEST_DEVICE_MODEL, PrebidServerSettings.deviceModel); // Default User Agent if (!TextUtils.isEmpty(PrebidServerSettings.userAgent)) { device.put(PrebidServerSettings.REQUEST_USERAGENT, PrebidServerSettings.userAgent); } // limited ad tracking device.put(PrebidServerSettings.REQUEST_LMT, AdvertisingIDUtil.isLimitAdTracking() ? 1 : 0); if(canIAccessDeviceData()) { if (!AdvertisingIDUtil.isLimitAdTracking() && !TextUtils.isEmpty(AdvertisingIDUtil.getAAID())) { // put ifa device.put(PrebidServerSettings.REQUEST_IFA, AdvertisingIDUtil.getAAID()); } } // os device.put(PrebidServerSettings.REQUEST_OS, PrebidServerSettings.os); device.put(PrebidServerSettings.REQUEST_OS_VERSION, String.valueOf(Build.VERSION.SDK_INT)); // language if (!TextUtils.isEmpty(Locale.getDefault().getLanguage())) { device.put(PrebidServerSettings.REQUEST_LANGUAGE, Locale.getDefault().getLanguage()); } if (adType.equals(AdType.INTERSTITIAL)) { Integer minSizePercWidth = null; Integer minSizePercHeight = null; AdSize minSizePerc = requestParams.getMinSizePerc(); if (minSizePerc != null) { minSizePercWidth = minSizePerc.getWidth(); minSizePercHeight = minSizePerc.getHeight(); } JSONObject deviceExt = new JSONObject(); JSONObject deviceExtPrebid = new JSONObject(); JSONObject deviceExtPrebidInstl = new JSONObject(); device.put("ext", deviceExt); deviceExt.put("prebid", deviceExtPrebid); deviceExtPrebid.put("interstitial", deviceExtPrebidInstl); deviceExtPrebidInstl.put("minwidthperc", minSizePercWidth); deviceExtPrebidInstl.put("minheightperc", minSizePercHeight); device.put("ext", deviceExt); } // POST data that requires context Context context = PrebidMobile.getApplicationContext(); if (context != null) { device.put(PrebidServerSettings.REQUEST_DEVICE_WIDTH, context.getResources().getConfiguration().screenWidthDp); device.put(PrebidServerSettings.REQUEST_DEVICE_HEIGHT, context.getResources().getConfiguration().screenHeightDp); device.put(PrebidServerSettings.REQUEST_DEVICE_PIXEL_RATIO, context.getResources().getDisplayMetrics().density); TelephonyManager telephonyManager = (TelephonyManager) context .getSystemService(Context.TELEPHONY_SERVICE); // Get mobile country codes if (PrebidServerSettings.getMCC() < 0 || PrebidServerSettings.getMNC() < 0) { String networkOperator = telephonyManager.getNetworkOperator(); if (!TextUtils.isEmpty(networkOperator)) { try { PrebidServerSettings.setMCC(Integer.parseInt(networkOperator.substring(0, 3))); PrebidServerSettings.setMNC(Integer.parseInt(networkOperator.substring(3))); } catch (Exception e) { // Catches NumberFormatException and StringIndexOutOfBoundsException PrebidServerSettings.setMCC(-1); PrebidServerSettings.setMNC(-1); } } } if (PrebidServerSettings.getMCC() > 0 && PrebidServerSettings.getMNC() > 0) { device.put(PrebidServerSettings.REQUEST_MCC_MNC, String.format(Locale.ENGLISH, "%d-%d", PrebidServerSettings.getMCC(), PrebidServerSettings.getMNC())); } // Get carrier if (PrebidServerSettings.getCarrierName() == null) { try { PrebidServerSettings.setCarrierName(telephonyManager.getNetworkOperatorName()); } catch (SecurityException ex) { // Some phones require READ_PHONE_STATE permission just ignore name PrebidServerSettings.setCarrierName(""); } } if (!TextUtils.isEmpty(PrebidServerSettings.getCarrierName())) device.put(PrebidServerSettings.REQUEST_CARRIER, PrebidServerSettings.getCarrierName()); // check connection type int connection_type = 0; ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); if (activeNetwork != null && activeNetwork.isConnected()) { NetworkInfo wifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI); if (wifi != null) { connection_type = wifi.isConnected() ? 1 : 2; } } device.put(PrebidServerSettings.REQUEST_CONNECTION_TYPE, connection_type); // get location // Do we have access to location? if (PrebidMobile.isShareGeoLocation()) { // get available location through Android LocationManager if (context.checkCallingOrSelfPermission("android.permission.ACCESS_FINE_LOCATION") == PackageManager.PERMISSION_GRANTED || context.checkCallingOrSelfPermission("android.permission.ACCESS_COARSE_LOCATION") == PackageManager.PERMISSION_GRANTED) { Location lastLocation = null; LocationManager lm = (LocationManager) context .getSystemService(Context.LOCATION_SERVICE); for (String provider_name : lm.getProviders(true)) { Location l = lm.getLastKnownLocation(provider_name); if (l == null) { continue; } if (lastLocation == null) { lastLocation = l; } else { if (l.getTime() > 0 && lastLocation.getTime() > 0) { if (l.getTime() > lastLocation.getTime()) { lastLocation = l; } } } } JSONObject geo = new JSONObject(); if (lastLocation != null) { Double lat = lastLocation.getLatitude(); Double lon = lastLocation.getLongitude(); geo.put(PrebidServerSettings.REQEUST_GEO_LAT, lat); geo.put(PrebidServerSettings.REQUEST_GEO_LON, lon); Integer locDataPrecision = Math.round(lastLocation.getAccuracy()); //Don't report location data from the future Integer locDataAge = (int) Math.max(0, (System.currentTimeMillis() - lastLocation.getTime())); geo.put(PrebidServerSettings.REQUEST_GEO_AGE, locDataAge); geo.put(PrebidServerSettings.REQUEST_GEO_ACCURACY, locDataPrecision); device.put(PrebidServerSettings.REQUEST_GEO, geo); } } else { LogUtil.w("Location permissions ACCESS_COARSE_LOCATION and/or ACCESS_FINE_LOCATION aren\\'t set in the host app. This may affect demand."); } } } } catch (JSONException e) { LogUtil.d("PrebidServerAdapter getDeviceObject() " + e.getMessage()); } return device; } private JSONObject getAppObject() { JSONObject app = new JSONObject(); try { if (!TextUtils.isEmpty(TargetingParams.getBundleName())) { app.put("bundle", TargetingParams.getBundleName()); } if (!TextUtils.isEmpty(PrebidServerSettings.pkgVersion)) { app.put("ver", PrebidServerSettings.pkgVersion); } if (!TextUtils.isEmpty(PrebidServerSettings.appName)) { app.put("name", PrebidServerSettings.appName); } if (!TextUtils.isEmpty(TargetingParams.getDomain())) { app.put("domain", TargetingParams.getDomain()); } if (!TextUtils.isEmpty(TargetingParams.getStoreUrl())) { app.put("storeurl", TargetingParams.getStoreUrl()); } JSONObject publisher = new JSONObject(); publisher.put("id", PrebidMobile.getPrebidServerAccountId()); app.put("publisher", publisher); JSONObject prebid = new JSONObject(); prebid.put("source", "prebid-mobile"); prebid.put("version", PrebidServerSettings.sdk_version); JSONObject ext = new JSONObject(); ext.put("prebid", prebid); ext.put("data", Util.toJson(TargetingParams.getContextDataDictionary())); app.put("ext", ext); app.put("keywords", TextUtils.join(",", TargetingParams.getContextKeywordsSet())); } catch (JSONException e) { LogUtil.d("PrebidServerAdapter getAppObject() " + e.getMessage()); } return app; } private JSONObject getUserObject() { JSONObject user = new JSONObject(); try { if (TargetingParams.getYearOfBirth() > 0) { user.put("yob", TargetingParams.getYearOfBirth()); } TargetingParams.GENDER gender = TargetingParams.getGender(); String g = "O"; switch (gender) { case FEMALE: g = "F"; break; case MALE: g = "M"; break; case UNKNOWN: g = "O"; break; } user.put("gender", g); String globalUserKeywordString = TextUtils.join(",", TargetingParams.getUserKeywordsSet()); user.put("keywords", globalUserKeywordString); JSONObject ext = new JSONObject(); Boolean isSubjectToGDPR = TargetingParams.isSubjectToGDPR(); if (Boolean.TRUE.equals(isSubjectToGDPR)) { ext.put("consent", TargetingParams.getGDPRConsentString()); } ext.put("data", Util.toJson(TargetingParams.getUserDataDictionary())); user.put("ext", ext); } catch (JSONException e) { LogUtil.d("PrebidServerAdapter getUserObject() " + e.getMessage()); } return user; } private JSONObject getRegsObject() { JSONObject regs = new JSONObject(); try { JSONObject ext = new JSONObject(); Boolean isSubjectToGDPR = TargetingParams.isSubjectToGDPR(); if (TargetingParams.isSubjectToCOPPA()) { regs.put("coppa", 1); } if (Boolean.TRUE.equals(isSubjectToGDPR)) { ext.put("gdpr", 1); } ext.put("us_privacy", StorageUtils.getIabCcpa()); regs.put("ext", ext); } catch (JSONException e) { LogUtil.d("PrebidServerAdapter getRegsObject() " + e.getMessage()); } return regs; } private boolean canIAccessDeviceData() { //fetch advertising identifier based TCF 2.0 Purpose1 value //truth table /* deviceAccessConsent=true deviceAccessConsent=false deviceAccessConsent undefined gdprApplies=false Yes, read IDFA No, don’t read IDFA Yes, read IDFA gdprApplies=true Yes, read IDFA No, don’t read IDFA No, don’t read IDFA gdprApplies=undefined Yes, read IDFA No, don’t read IDFA Yes, read IDFA */ boolean setDeviceId = false; Boolean gdprApplies = TargetingParams.isSubjectToGDPR(); Boolean deviceAccessConsent = TargetingParams.getDeviceAccessConsent(); if((deviceAccessConsent == null && (gdprApplies == null || Boolean.FALSE.equals(gdprApplies))) || Boolean.TRUE.equals(deviceAccessConsent)) { setDeviceId = true; } return setDeviceId; } private static class NoContextException extends Exception { } private static class AsyncTaskResult<T> { @Nullable private T result; @Nullable private ResultCode resultCode; @Nullable private Exception error; @Nullable public T getResult() { return result; } @Nullable public ResultCode getResultCode() { return resultCode; } @Nullable public Exception getError() { return error; } private AsyncTaskResult(@NonNull T result) { this.result = result; } private AsyncTaskResult(@NonNull ResultCode resultCode) { this.resultCode = resultCode; } private AsyncTaskResult(@NonNull Exception error) { this.error = error; } } class TimeoutCountDownTimer extends CountDownTimer { /** * @param millisInFuture The number of millis in the future from the call * to {@link #start()} until the countdown is done and {@link #onFinish()} * is called. * @param countDownInterval The interval along the way to receive * {@link #onTick(long)} callbacks. */ public TimeoutCountDownTimer(long millisInFuture, long countDownInterval) { super(millisInFuture, countDownInterval); } @Override public void onTick(long millisUntilFinished) { if (ServerConnector.this.isCancelled()) { TimeoutCountDownTimer.this.cancel(); } } @Override public void onFinish() { if (ServerConnector.this.isCancelled()) { return; } timeoutFired = true; ServerConnector.this.cancel(true); } } } }