Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2dfae393 authored by Edgar Wang's avatar Edgar Wang Committed by Android (Google) Code Review
Browse files

Merge "Remove SettingsIntelligence log writer" into main

parents 73a95d16 3755ba0f
Loading
Loading
Loading
Loading
+0 −211
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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 com.android.settings.core.instrumentation;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Pair;

import androidx.annotation.VisibleForTesting;

import com.android.settings.R;
import com.android.settings.intelligence.LogProto.SettingsLog;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.LogWriter;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.LinkedList;
import java.util.List;

public class SettingsIntelligenceLogWriter implements LogWriter {
    private static final String TAG = "IntelligenceLogWriter";

    private static final String LOG = "logs";
    private static final long MESSAGE_DELAY = DateUtils.MINUTE_IN_MILLIS; // 1 minute
    // Based on the exp, 99.5% users collect less than 150 data in 1 minute.
    private static final int CACHE_LOG_THRESHOLD = 150;

    private List<SettingsLog> mSettingsLogList;
    private SendLogHandler mLogHandler;

    public SettingsIntelligenceLogWriter() {
        mSettingsLogList = new LinkedList<>();
        final HandlerThread workerThread = new HandlerThread("SettingsIntelligenceLogWriter",
                Process.THREAD_PRIORITY_BACKGROUND);
        workerThread.start();
        mLogHandler = new SendLogHandler(workerThread.getLooper());
    }

    @Override
    public void visible(Context context, int attribution, int pageId, int latency) {
        action(attribution /* from pageId */,
                SettingsEnums.PAGE_VISIBLE /* action */,
                pageId /* target pageId */,
                "" /* changedPreferenceKey */,
                latency /* changedPreferenceIntValue */);
    }

    @Override
    public void hidden(Context context, int pageId, int visibleTime) {
        action(SettingsEnums.PAGE_UNKNOWN /* attribution */,
                SettingsEnums.PAGE_HIDE /* action */,
                pageId /* pageId */,
                "" /* changedPreferenceKey */,
                visibleTime /* changedPreferenceIntValue */);
    }

    @Override
    public void clicked(int sourceCategory, String key) {
    }

    @Override
    public void changed(int category, String key, int value) {
    }

    @Override
    public void action(Context context, int action, Pair<Integer, Object>... taggedData) {
        action(SettingsEnums.PAGE_UNKNOWN /* attribution */,
                action,
                SettingsEnums.PAGE_UNKNOWN /* pageId */,
                "" /* changedPreferenceKey */,
                0 /* changedPreferenceIntValue */);
    }

    @Override
    public void action(Context context, int action, int value) {
        action(SettingsEnums.PAGE_UNKNOWN /* attribution */,
                action,
                SettingsEnums.PAGE_UNKNOWN /* pageId */,
                "" /* changedPreferenceKey */,
                value /* changedPreferenceIntValue */);
    }

    @Override
    public void action(Context context, int action, boolean value) {
        action(SettingsEnums.PAGE_UNKNOWN /* attribution */,
                action,
                SettingsEnums.PAGE_UNKNOWN /* pageId */,
                "" /* changedPreferenceKey */,
                value ? 1 : 0 /* changedPreferenceIntValue */);
    }

    @Override
    public void action(Context context, int action, String pkg) {
        action(SettingsEnums.PAGE_UNKNOWN /* attribution */,
                action,
                SettingsEnums.PAGE_UNKNOWN /* pageId */,
                pkg /* changedPreferenceKey */,
                1 /* changedPreferenceIntValue */);
    }

    @Override
    public void action(int attribution, int action, int pageId, String key, int value) {
        final ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
        final SettingsLog settingsLog = SettingsLog.newBuilder()
                .setAttribution(attribution)
                .setAction(action)
                .setPageId(pageId)
                .setChangedPreferenceKey(key != null ? key : "")
                .setChangedPreferenceIntValue(value)
                .setTimestamp(now.toString())
                .build();
        mLogHandler.post(() -> {
            mSettingsLogList.add(settingsLog);
        });
        if (action == SettingsEnums.ACTION_CONTEXTUAL_CARD_DISMISS
                || mSettingsLogList.size() >= CACHE_LOG_THRESHOLD) {
            // Directly send this event to notify SI instantly that the card is dismissed
            mLogHandler.sendLog();
        } else {
            mLogHandler.scheduleSendLog();
        }
    }

    @VisibleForTesting
    static byte[] serialize(List<SettingsLog> settingsLogs) {
        final int size = settingsLogs.size();
        final ByteArrayOutputStream bout = new ByteArrayOutputStream();
        final DataOutputStream output = new DataOutputStream(bout);
        // The data format is "size, length, byte array, length, byte array ..."
        try {
            output.writeInt(size);
            for (SettingsLog settingsLog : settingsLogs) {
                final byte[] data = settingsLog.toByteArray();
                output.writeInt(data.length);
                output.write(data);
            }
            return bout.toByteArray();
        } catch (Exception e) {
            Log.e(TAG, "serialize error", e);
            return null;
        } finally {
            try {
                output.close();
            } catch (Exception e) {
                Log.e(TAG, "close error", e);
            }
        }
    }

    private class SendLogHandler extends Handler {

        SendLogHandler(Looper looper) {
            super(looper);
        }

        void scheduleSendLog() {
            removeCallbacks(mSendLogsRunnable);
            postDelayed(mSendLogsRunnable, MESSAGE_DELAY);
        }

        void sendLog() {
            removeCallbacks(mSendLogsRunnable);
            post(mSendLogsRunnable);
        }
    }

    private final Runnable mSendLogsRunnable = () -> {
        final Context context = FeatureFactory.getAppContext();
        if (context == null) {
            Log.e(TAG, "context is null");
            return;
        }
        final String action = context.getString(R.string
                .config_settingsintelligence_log_action);
        if (!TextUtils.isEmpty(action) && !mSettingsLogList.isEmpty()) {
            final Intent intent = new Intent();
            intent.setPackage(context.getString(R.string
                    .config_settingsintelligence_package_name));
            intent.setAction(action);
            intent.putExtra(LOG, serialize(mSettingsLogList));
            context.sendBroadcastAsUser(intent, UserHandle.CURRENT);
            mSettingsLogList.clear();
        }
    };
}
+0 −1
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ public class SettingsMetricsFeatureProvider extends MetricsFeatureProvider {
    protected void installLogWriters() {
        mLoggerWriters.add(new StatsLogWriter());
        mLoggerWriters.add(new SettingsEventLogWriter());
        mLoggerWriters.add(new SettingsIntelligenceLogWriter());
    }

    /**
+0 −78
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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 com.android.settings.core.instrumentation;

import static com.google.common.truth.Truth.assertThat;

import android.app.settings.SettingsEnums;
import android.content.Context;

import com.android.settings.intelligence.LogProto.SettingsLog;

import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class SettingsIntelligenceLogWriterTest {
    private Context mContext;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;
    }

    @Test
    public void serialize_hasSizeOne_returnCorrectData() throws IOException {
        final SettingsLog event = SettingsLog.newBuilder()
                .setAttribution(SettingsEnums.DASHBOARD_SUMMARY)
                .setAction(SettingsEnums.ACTION_SET_NEW_PASSWORD)
                .setPageId(SettingsEnums.SET_NEW_PASSWORD_ACTIVITY)
                .setChangedPreferenceKey("package")
                .setChangedPreferenceIntValue(100)
                .build();
        List<SettingsLog> events = new ArrayList<>();
        events.add(event);

        // execute
        final byte[] data = SettingsIntelligenceLogWriter.serialize(events);

        // parse data
        final ByteArrayInputStream bin = new ByteArrayInputStream(data);
        final DataInputStream inputStream = new DataInputStream(bin);
        final int size = inputStream.readInt();
        final byte[] change = new byte[inputStream.readInt()];
        inputStream.read(change);
        inputStream.close();
        final SettingsLog settingsLog = SettingsLog.parseFrom(change);

        // assert
        assertThat(events.size()).isEqualTo(size);
        assertThat(settingsLog.getAttribution()).isEqualTo(SettingsEnums.DASHBOARD_SUMMARY);
        assertThat(settingsLog.getAction()).isEqualTo(SettingsEnums.ACTION_SET_NEW_PASSWORD);
        assertThat(settingsLog.getPageId()).isEqualTo(SettingsEnums.SET_NEW_PASSWORD_ACTIVITY);
        assertThat(settingsLog.getChangedPreferenceKey()).isEqualTo("package");
        assertThat(settingsLog.getChangedPreferenceIntValue()).isEqualTo(100);
    }
}