chromium/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/UsageStatsBridge.java

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.usage_stats;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;

import org.chromium.base.Callback;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.usage_stats.WebsiteEventProtos.WebsiteEvent;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/** Provides access to native implementation of usage stats storage. */
@JNINamespace("usage_stats")
public class UsageStatsBridge {
    private final UsageStatsService mUsageStatsService;

    private long mNativeUsageStatsBridge;

    /**
     * Creates a {@link UsageStatsBridge} for accessing native usage stats storage implementation
     * for the current user, and initial native side bridge.
     *
     * @param profile {@link Profile} of the user for whom we are tracking usage stats.
     */
    public UsageStatsBridge(Profile profile, UsageStatsService usageStatsService) {
        mNativeUsageStatsBridge = UsageStatsBridgeJni.get().init(UsageStatsBridge.this, profile);
        mUsageStatsService = usageStatsService;
    }

    /** Cleans up native side of this bridge. */
    public void destroy() {
        assert mNativeUsageStatsBridge != 0;
        UsageStatsBridgeJni.get().destroy(mNativeUsageStatsBridge, UsageStatsBridge.this);
        mNativeUsageStatsBridge = 0;
    }

    public void getAllEvents(Callback<List<WebsiteEvent>> callback) {
        assert mNativeUsageStatsBridge != 0;
        UsageStatsBridgeJni.get()
                .getAllEvents(mNativeUsageStatsBridge, UsageStatsBridge.this, callback);
    }

    public void queryEventsInRange(
            long startMs, long endMs, Callback<List<WebsiteEvent>> callback) {
        assert mNativeUsageStatsBridge != 0;
        long startSeconds = TimeUnit.MILLISECONDS.toSeconds(startMs);
        long endSeconds = TimeUnit.MILLISECONDS.toSeconds(endMs);
        UsageStatsBridgeJni.get()
                .queryEventsInRange(
                        mNativeUsageStatsBridge,
                        UsageStatsBridge.this,
                        startSeconds,
                        endSeconds,
                        callback);
    }

    public void addEvents(List<WebsiteEvent> events, Callback<Boolean> callback) {
        assert mNativeUsageStatsBridge != 0;

        byte[][] serializedEvents = new byte[events.size()][];

        for (int i = 0; i < events.size(); i++) {
            WebsiteEvent event = events.get(i);
            serializedEvents[i] = event.toByteArray();
        }

        UsageStatsBridgeJni.get()
                .addEvents(
                        mNativeUsageStatsBridge, UsageStatsBridge.this, serializedEvents, callback);
    }

    public void deleteAllEvents(Callback<Boolean> callback) {
        assert mNativeUsageStatsBridge != 0;
        UsageStatsBridgeJni.get()
                .deleteAllEvents(mNativeUsageStatsBridge, UsageStatsBridge.this, callback);
    }

    public void deleteEventsInRange(long startMs, long endMs, Callback<Boolean> callback) {
        assert mNativeUsageStatsBridge != 0;
        long startSeconds = TimeUnit.MILLISECONDS.toSeconds(startMs);
        long endSeconds = TimeUnit.MILLISECONDS.toSeconds(endMs);
        UsageStatsBridgeJni.get()
                .deleteEventsInRange(
                        mNativeUsageStatsBridge,
                        UsageStatsBridge.this,
                        startSeconds,
                        endSeconds,
                        callback);
    }

    public void deleteEventsWithMatchingDomains(String[] domains, Callback<Boolean> callback) {
        assert mNativeUsageStatsBridge != 0;
        UsageStatsBridgeJni.get()
                .deleteEventsWithMatchingDomains(
                        mNativeUsageStatsBridge, UsageStatsBridge.this, domains, callback);
    }

    public void getAllSuspensions(Callback<List<String>> callback) {
        assert mNativeUsageStatsBridge != 0;
        UsageStatsBridgeJni.get()
                .getAllSuspensions(
                        mNativeUsageStatsBridge,
                        UsageStatsBridge.this,
                        arr -> {
                            callback.onResult(new ArrayList<>(Arrays.asList(arr)));
                        });
    }

    public void setSuspensions(String[] domains, Callback<Boolean> callback) {
        assert mNativeUsageStatsBridge != 0;
        UsageStatsBridgeJni.get()
                .setSuspensions(mNativeUsageStatsBridge, UsageStatsBridge.this, domains, callback);
    }

    public void getAllTokenMappings(Callback<Map<String, String>> callback) {
        assert mNativeUsageStatsBridge != 0;
        UsageStatsBridgeJni.get()
                .getAllTokenMappings(mNativeUsageStatsBridge, UsageStatsBridge.this, callback);
    }

    public void setTokenMappings(Map<String, String> mappings, Callback<Boolean> callback) {
        assert mNativeUsageStatsBridge != 0;

        // Split map into separate String arrays of tokens (keys) and FQDNs (values) for passing
        // over JNI bridge.
        String[] tokens = new String[mappings.size()];
        String[] fqdns = new String[mappings.size()];

        int i = 0;
        for (Map.Entry<String, String> entry : mappings.entrySet()) {
            tokens[i] = entry.getKey();
            fqdns[i] = entry.getValue();
            i++;
        }

        UsageStatsBridgeJni.get()
                .setTokenMappings(
                        mNativeUsageStatsBridge, UsageStatsBridge.this, tokens, fqdns, callback);
    }

    @CalledByNative
    private static void createMapAndRunCallback(
            String[] keys, String[] values, Callback<Map<String, String>> callback) {
        assert keys.length == values.length;
        Map<String, String> map = new HashMap<>(keys.length);
        for (int i = 0; i < keys.length; i++) {
            map.put(keys[i], values[i]);
        }

        callback.onResult(map);
    }

    @CalledByNative
    private static void createEventListAndRunCallback(
            byte[][] serializedEvents, Callback<List<WebsiteEvent>> callback) {
        List<WebsiteEvent> events = new ArrayList<>(serializedEvents.length);

        for (byte[] serialized : serializedEvents) {
            try {
                WebsiteEvent event = WebsiteEvent.parseFrom(serialized);
                events.add(event);
            } catch (com.google.protobuf.InvalidProtocolBufferException e) {
                // Consume exception for now, ignoring unparseable events.
            }
        }

        callback.onResult(events);
    }

    @CalledByNative
    private void onAllHistoryDeleted() {
        mUsageStatsService.onAllHistoryDeleted();
    }

    @CalledByNative
    private void onHistoryDeletedInRange(long startTimeMs, long endTimeMs) {
        mUsageStatsService.onHistoryDeletedInRange(startTimeMs, endTimeMs);
    }

    @CalledByNative
    private void onHistoryDeletedForDomains(String[] fqdns) {
        mUsageStatsService.onHistoryDeletedForDomains(new ArrayList<>(Arrays.asList(fqdns)));
    }

    @NativeMethods
    interface Natives {
        long init(UsageStatsBridge caller, @JniType("Profile*") Profile profile);

        void destroy(long nativeUsageStatsBridge, UsageStatsBridge caller);

        void getAllEvents(
                long nativeUsageStatsBridge,
                UsageStatsBridge caller,
                Callback<List<WebsiteEvent>> callback);

        void queryEventsInRange(
                long nativeUsageStatsBridge,
                UsageStatsBridge caller,
                long start,
                long end,
                Callback<List<WebsiteEvent>> callback);

        void addEvents(
                long nativeUsageStatsBridge,
                UsageStatsBridge caller,
                byte[][] events,
                Callback<Boolean> callback);

        void deleteAllEvents(
                long nativeUsageStatsBridge, UsageStatsBridge caller, Callback<Boolean> callback);

        void deleteEventsInRange(
                long nativeUsageStatsBridge,
                UsageStatsBridge caller,
                long start,
                long end,
                Callback<Boolean> callback);

        void deleteEventsWithMatchingDomains(
                long nativeUsageStatsBridge,
                UsageStatsBridge caller,
                String[] domains,
                Callback<Boolean> callback);

        void getAllSuspensions(
                long nativeUsageStatsBridge, UsageStatsBridge caller, Callback<String[]> callback);

        void setSuspensions(
                long nativeUsageStatsBridge,
                UsageStatsBridge caller,
                String[] domains,
                Callback<Boolean> callback);

        void getAllTokenMappings(
                long nativeUsageStatsBridge,
                UsageStatsBridge caller,
                Callback<Map<String, String>> callback);

        void setTokenMappings(
                long nativeUsageStatsBridge,
                UsageStatsBridge caller,
                String[] tokens,
                String[] fqdns,
                Callback<Boolean> callback);
    }
}