/** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, * copy, modify, and distribute this software in source code or binary form for use * in connection with the web services and APIs provided by Facebook. * * As with any software that integrates with the Facebook platform, your use of * this software is subject to the Facebook Developer Principles and Policies * [http://developers.facebook.com/policy/]. This copyright notice shall be * included in all copies or substantial portions of the software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.facebook.appevents; import android.content.Context; import android.os.Bundle; import com.facebook.GraphRequest; import com.facebook.internal.AppEventsLoggerUtility; import com.facebook.internal.AttributionIdentifiers; import com.facebook.internal.Utility; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; class SessionEventsState { private List<AppEvent> accumulatedEvents = new ArrayList<AppEvent>(); private List<AppEvent> inFlightEvents = new ArrayList<AppEvent>(); private int numSkippedEventsDueToFullBuffer; private AttributionIdentifiers attributionIdentifiers; private String anonymousAppDeviceGUID; private final int MAX_ACCUMULATED_LOG_EVENTS = 1000; public SessionEventsState( AttributionIdentifiers identifiers, String anonymousGUID) { this.attributionIdentifiers = identifiers; this.anonymousAppDeviceGUID = anonymousGUID; } // Synchronize here and in other methods on this class, because could be coming in from // different AppEventsLoggers on different threads pointing at the same session. public synchronized void addEvent(AppEvent event) { if (accumulatedEvents.size() + inFlightEvents.size() >= MAX_ACCUMULATED_LOG_EVENTS) { numSkippedEventsDueToFullBuffer++; } else { accumulatedEvents.add(event); } } public synchronized int getAccumulatedEventCount() { return accumulatedEvents.size(); } public synchronized void clearInFlightAndStats(boolean moveToAccumulated) { if (moveToAccumulated) { accumulatedEvents.addAll(inFlightEvents); } inFlightEvents.clear(); numSkippedEventsDueToFullBuffer = 0; } public int populateRequest( GraphRequest request, Context applicationContext, boolean includeImplicitEvents, boolean limitEventUsage) { int numSkipped; JSONArray jsonArray; synchronized (this) { numSkipped = numSkippedEventsDueToFullBuffer; // move all accumulated events to inFlight. inFlightEvents.addAll(accumulatedEvents); accumulatedEvents.clear(); jsonArray = new JSONArray(); for (AppEvent event : inFlightEvents) { if (event.isChecksumValid()) { if (includeImplicitEvents || !event.getIsImplicit()) { jsonArray.put(event.getJSONObject()); } } else { Utility.logd("Event with invalid checksum: %s", event.toString()); } } if (jsonArray.length() == 0) { return 0; } } populateRequest( request, applicationContext, numSkipped, jsonArray, limitEventUsage); return jsonArray.length(); } public synchronized List<AppEvent> getEventsToPersist() { // We will only persist accumulated events, not ones currently in-flight. This means if // an in-flight request fails, those requests will not be persisted and thus might be // lost if the process terminates while the flush is in progress. List<AppEvent> result = accumulatedEvents; accumulatedEvents = new ArrayList<AppEvent>(); return result; } public synchronized void accumulatePersistedEvents(List<AppEvent> events) { // We won't skip events due to a full buffer, since we already accumulated them once and // persisted them. But they will count against the buffer size when further events are // accumulated. accumulatedEvents.addAll(events); } private void populateRequest( GraphRequest request, Context applicationContext, int numSkipped, JSONArray events, boolean limitEventUsage) { JSONObject publishParams = null; try { publishParams = AppEventsLoggerUtility.getJSONObjectForGraphAPICall( AppEventsLoggerUtility.GraphAPIActivityType.CUSTOM_APP_EVENTS, attributionIdentifiers, anonymousAppDeviceGUID, limitEventUsage, applicationContext); if (numSkippedEventsDueToFullBuffer > 0) { publishParams.put("num_skipped_events", numSkipped); } } catch (JSONException e) { // Swallow publishParams = new JSONObject(); } request.setGraphObject(publishParams); Bundle requestParameters = request.getParameters(); if (requestParameters == null) { requestParameters = new Bundle(); } String jsonString = events.toString(); if (jsonString != null) { requestParameters.putByteArray( "custom_events_file", getStringAsByteArray(jsonString)); request.setTag(jsonString); } request.setParameters(requestParameters); } private byte[] getStringAsByteArray(String jsonString) { byte[] jsonUtf8 = null; try { jsonUtf8 = jsonString.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { // shouldn't happen, but just in case: Utility.logd("Encoding exception: ", e); } return jsonUtf8; } }