Add SettingsIntelligenceLogwriter

Bug: 124701288
Test: robolectric, manual
Change-Id: Iea446ae600d22ed62c5ee45afd1cd27a89b5de34
This commit is contained in:
Raff Tsai
2019-01-30 11:14:20 +08:00
parent aa0d851e11
commit 02e45574a3
7 changed files with 319 additions and 0 deletions

View File

@@ -47,6 +47,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
guava \ guava \
jsr305 \ jsr305 \
settings-contextual-card-protos-lite \ settings-contextual-card-protos-lite \
settings-log-bridge-protos-lite \
contextualcards \ contextualcards \
settings-logtags \ settings-logtags \
zxing-core-1.7 zxing-core-1.7

View File

@@ -6,3 +6,12 @@ java_library_static {
}, },
srcs: ["contextual_card_list.proto"], srcs: ["contextual_card_list.proto"],
} }
java_library_static {
name: "settings-log-bridge-protos-lite",
host_supported: true,
proto: {
type: "lite",
},
srcs: ["settings_log_bridge.proto"],
}

View File

@@ -0,0 +1,37 @@
syntax = "proto2";
package com.android.settings.intelligence;
option java_outer_classname = "LogProto";
message SettingsLog {
/**
* Where this SettingsUIChange event comes from. For example, if
* it's a PAGE_VISIBLE event, where the page is opened from.
*/
optional int32 attribution = 1;
/**
* What the UI action is.
*/
optional int32 action = 2;
/**
* Where the action is happening
*/
optional int32 page_id = 3;
/**
* What preference changed in this event.
*/
optional string changed_preference_key = 4;
/**
* The new value of the changed preference.
*/
optional int32 changed_preference_int_value = 5;
/**
* The timestamp of a log event
*/
optional string timestamp = 6;
}

View File

@@ -0,0 +1,190 @@
/*
* 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
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) {
action(attribution /* attribution */,
SettingsEnums.PAGE_VISIBLE /* action */,
pageId /* pageId */,
"" /* changedPreferenceKey */,
0 /* changedPreferenceIntValue */);
}
@Override
public void hidden(Context context, int pageId) {
action(SettingsEnums.PAGE_UNKNOWN /* attribution */,
SettingsEnums.PAGE_HIDE /* action */,
pageId /* pageId */,
"" /* changedPreferenceKey */,
0 /* changedPreferenceIntValue */);
}
@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);
});
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);
// Data is "size, length, bytearray, length, bytearray ..."
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 {
public SendLogHandler(Looper looper) {
super(looper);
}
public void scheduleSendLog() {
removeCallbacks(mSendLogsRunnable);
postDelayed(mSendLogsRunnable, MESSAGE_DELAY);
}
}
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();
}
};
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.core.instrumentation; package com.android.settings.core.instrumentation;
import android.content.Context;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
public class SettingsMetricsFeatureProvider extends MetricsFeatureProvider { public class SettingsMetricsFeatureProvider extends MetricsFeatureProvider {
@@ -23,5 +25,6 @@ public class SettingsMetricsFeatureProvider extends MetricsFeatureProvider {
protected void installLogWriters() { protected void installLogWriters() {
super.installLogWriters(); super.installLogWriters();
mLoggerWriters.add(new StatsLogWriter()); mLoggerWriters.add(new StatsLogWriter());
mLoggerWriters.add(new SettingsIntelligenceLogWriter());
} }
} }

View File

@@ -49,6 +49,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
guava \ guava \
jsr305 \ jsr305 \
settings-contextual-card-protos-lite \ settings-contextual-card-protos-lite \
settings-log-bridge-protos-lite \
contextualcards \ contextualcards \
settings-logtags \ settings-logtags \
zxing-core-1.7 zxing-core-1.7

View File

@@ -0,0 +1,78 @@
/*
* 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);
}
}