App 2.19.9: remove the Foreground apps card from the Logs tab
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "com.stinnesbeck.nilsmg4"
|
||||
minSdk 27
|
||||
targetSdk 36
|
||||
versionCode 107
|
||||
versionName "2.19.8"
|
||||
versionCode 108
|
||||
versionName "2.19.9"
|
||||
|
||||
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
||||
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Which app was in front, and when: the lines of the Logs tab's "Foreground apps" card.
|
||||
*
|
||||
* Built from the platform's foreground events, oldest first. Consecutive events for the same
|
||||
* screen collapse into one, the newest comes first, and an activity in its own package is
|
||||
* shortened to ".Name" so the line stays readable. Pure Java, so it is unit-tested.
|
||||
*/
|
||||
final class ForegroundHistory {
|
||||
|
||||
/** One move to the foreground. className may be null when the platform gives none. */
|
||||
static final class Event {
|
||||
final long timeMs;
|
||||
final String packageName;
|
||||
final String className;
|
||||
|
||||
Event(long timeMs, String packageName, String className) {
|
||||
this.timeMs = timeMs;
|
||||
this.packageName = packageName;
|
||||
this.className = className;
|
||||
}
|
||||
}
|
||||
|
||||
private ForegroundHistory() {}
|
||||
|
||||
/** At most {@code limit} lines, newest first. */
|
||||
static List<String> lines(List<Event> oldestFirst, int limit, TimeZone zone) {
|
||||
List<Event> collapsed = new ArrayList<>();
|
||||
for (Event e : oldestFirst) {
|
||||
if (e == null || e.packageName == null || e.packageName.isEmpty()) continue;
|
||||
Event last = collapsed.isEmpty() ? null : collapsed.get(collapsed.size() - 1);
|
||||
if (last != null && last.packageName.equals(e.packageName)
|
||||
&& equal(last.className, e.className)) continue;
|
||||
collapsed.add(e);
|
||||
}
|
||||
SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss", Locale.ROOT);
|
||||
time.setTimeZone(zone);
|
||||
List<String> out = new ArrayList<>();
|
||||
for (int i = collapsed.size() - 1; i >= 0 && out.size() < limit; i--) {
|
||||
Event e = collapsed.get(i);
|
||||
String screen = shortClass(e.packageName, e.className);
|
||||
out.add(time.format(e.timeMs) + " " + e.packageName + (screen == null ? "" : " " + screen));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** ".Name" for an activity inside its own package, the full name otherwise. */
|
||||
static String shortClass(String packageName, String className) {
|
||||
if (className == null || className.isEmpty()) return null;
|
||||
if (className.startsWith(packageName + ".")) return className.substring(packageName.length());
|
||||
return className;
|
||||
}
|
||||
|
||||
private static boolean equal(String a, String b) {
|
||||
return a == null ? b == null : a.equals(b);
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.usage.UsageEvents;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Read-only diagnostic: which apps were in front lately, which tasks are open now, and the
|
||||
* system values a full-screen experiment for another app would touch. It changes nothing.
|
||||
*
|
||||
* The usage events and the full task list are open to the system user, which the car build
|
||||
* runs as; the stable build sees only its own tasks and no events, and says so. Called on
|
||||
* demand from the Logs tab, never in the background.
|
||||
*/
|
||||
final class ForegroundProbe {
|
||||
|
||||
private static final String TAG = "EVABRP.Foreground";
|
||||
static final long WINDOW_MS = 30 * 60_000L;
|
||||
private static final int MAX_LINES = 8;
|
||||
private static final int MAX_TASKS = 4;
|
||||
|
||||
static final class Result {
|
||||
/** Null when the platform would not give the events (not the system user). */
|
||||
List<String> recent;
|
||||
List<String> tasks = new ArrayList<>();
|
||||
/** The {@code policy_control} global setting; null when unset. */
|
||||
String policyControl;
|
||||
/** The platform's status bar height in pixels; null when unknown. */
|
||||
Integer statusBarPx;
|
||||
}
|
||||
|
||||
private ForegroundProbe() {}
|
||||
|
||||
static Result read(Context context) {
|
||||
Result r = new Result();
|
||||
r.recent = recentForeground(context);
|
||||
r.tasks = runningTasks(context);
|
||||
try {
|
||||
r.policyControl = Settings.Global.getString(context.getContentResolver(), "policy_control");
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "policy_control unreadable", e);
|
||||
}
|
||||
try {
|
||||
Resources sys = Resources.getSystem();
|
||||
int id = sys.getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (id != 0) r.statusBarPx = sys.getDimensionPixelSize(id);
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "status bar height unreadable", e);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// No PACKAGE_USAGE_STATS in the manifest: the usage service lets the system user through on
|
||||
// its own, and any other caller gets an empty stream, reported as "not readable".
|
||||
@SuppressLint("MissingPermission")
|
||||
private static List<String> recentForeground(Context context) {
|
||||
try {
|
||||
UsageStatsManager usage = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
|
||||
if (usage == null) return null;
|
||||
long now = System.currentTimeMillis();
|
||||
UsageEvents events = usage.queryEvents(now - WINDOW_MS, now);
|
||||
if (events == null) return null;
|
||||
List<ForegroundHistory.Event> list = new ArrayList<>();
|
||||
UsageEvents.Event e = new UsageEvents.Event();
|
||||
boolean any = false;
|
||||
while (events.hasNextEvent()) {
|
||||
events.getNextEvent(e);
|
||||
any = true;
|
||||
if (e.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
|
||||
list.add(new ForegroundHistory.Event(e.getTimeStamp(), e.getPackageName(), e.getClassName()));
|
||||
}
|
||||
}
|
||||
// A caller without access gets an empty stream rather than an error; an empty
|
||||
// stream for the system user would still contain our own app coming to the front.
|
||||
if (!any) return null;
|
||||
return ForegroundHistory.lines(list, MAX_LINES, TimeZone.getDefault());
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "usage events unreadable", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // getRunningTasks: the system user still gets every task
|
||||
private static List<String> runningTasks(Context context) {
|
||||
List<String> out = new ArrayList<>();
|
||||
try {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (am == null) return out;
|
||||
for (ActivityManager.RunningTaskInfo t : am.getRunningTasks(MAX_TASKS)) {
|
||||
ComponentName top = t.topActivity;
|
||||
if (top == null) continue;
|
||||
String screen = ForegroundHistory.shortClass(top.getPackageName(), top.getClassName());
|
||||
out.add(top.getPackageName() + (screen == null ? "" : " " + screen));
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "tasks unreadable", e);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -203,47 +203,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
((TextView) findViewById(R.id.header_title)).setText(PAGE_TITLES[position]);
|
||||
((TextView) findViewById(R.id.header_subtitle)).setText(PAGE_SUBTITLES[position]);
|
||||
if (panes != null && panes[position] == logPane) refreshForeground();
|
||||
}
|
||||
|
||||
private TextView foregroundText;
|
||||
private final java.util.concurrent.ExecutorService diagReader =
|
||||
java.util.concurrent.Executors.newSingleThreadExecutor(r -> new Thread(r, "foreground-read"));
|
||||
|
||||
/** Logs tab diagnostic: reads which apps were in front, off the main thread. Changes nothing. */
|
||||
private void refreshForeground() {
|
||||
if (foregroundText == null) return;
|
||||
try {
|
||||
diagReader.execute(() -> {
|
||||
ForegroundProbe.Result r = ForegroundProbe.read(getApplicationContext());
|
||||
runOnUiThread(() -> { if (!isDestroyed()) foregroundText.setText(foregroundReport(r)); });
|
||||
});
|
||||
} catch (java.util.concurrent.RejectedExecutionException ignored) {
|
||||
// Activity going away.
|
||||
}
|
||||
}
|
||||
|
||||
private String foregroundReport(ForegroundProbe.Result r) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (r.recent == null) {
|
||||
sb.append(getString(R.string.foreground_unavailable)).append('\n');
|
||||
} else if (r.recent.isEmpty()) {
|
||||
sb.append(getString(R.string.foreground_none)).append('\n');
|
||||
} else {
|
||||
sb.append(getString(R.string.foreground_recent)).append('\n');
|
||||
for (String line : r.recent) sb.append(line).append('\n');
|
||||
}
|
||||
if (!r.tasks.isEmpty()) {
|
||||
sb.append('\n').append(getString(R.string.foreground_tasks)).append('\n');
|
||||
for (String t : r.tasks) sb.append(t).append('\n');
|
||||
}
|
||||
sb.append('\n').append(getString(R.string.foreground_policy,
|
||||
r.policyControl == null ? getString(R.string.foreground_policy_unset) : r.policyControl));
|
||||
if (r.statusBarPx != null) {
|
||||
sb.append('\n').append(getString(R.string.foreground_status_bar, r.statusBarPx));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
/** Background thread for the one system-setting write (clearCarPlayFullScreen). */
|
||||
private final java.util.concurrent.ExecutorService settingsWriter =
|
||||
java.util.concurrent.Executors.newSingleThreadExecutor(r -> new Thread(r, "settings-write"));
|
||||
|
||||
/** Refreshes state + call log while the screen is visible. */
|
||||
private final android.os.Handler uiHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
||||
@@ -296,8 +260,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
statusText = servicePane.findViewById(R.id.status_text);
|
||||
callLogText = logPane.findViewById(R.id.call_log_text);
|
||||
logPane.findViewById(R.id.flush_now_button).setOnClickListener(v -> manualFlush());
|
||||
foregroundText = logPane.findViewById(R.id.foreground_text);
|
||||
logPane.findViewById(R.id.foreground_refresh_button).setOnClickListener(v -> refreshForeground());
|
||||
|
||||
setUpPager();
|
||||
// Back on the page that was open when the activity was recreated (accent colour change).
|
||||
@@ -386,11 +348,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
// Poll state and log only while the screen is up; onPause cancels it.
|
||||
uiHandler.post(uiRefresh);
|
||||
uiHandler.post(overviewRefresh);
|
||||
// Back from another app (CarPlay) on the Logs tab: show where we were.
|
||||
if (panes != null) {
|
||||
ViewPager2 pager = findViewById(R.id.content);
|
||||
if (panes[pager.getCurrentItem()] == logPane) refreshForeground();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -443,7 +400,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
carReader.shutdownNow();
|
||||
diagReader.shutdownNow();
|
||||
settingsWriter.shutdownNow();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -1076,7 +1033,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
private void clearCarPlayFullScreen() {
|
||||
if (checkSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
|
||||
!= android.content.pm.PackageManager.PERMISSION_GRANTED) return;
|
||||
diagReader.execute(() -> {
|
||||
settingsWriter.execute(() -> {
|
||||
try {
|
||||
android.content.ContentResolver cr = getContentResolver();
|
||||
String before = android.provider.Settings.Global.getString(cr, PolicyControl.KEY);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.evsuite.abrp.StableScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:clipToPadding="false">
|
||||
<!-- Two columns: upload log | foreground apps (diagnostic). -->
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="12dp" android:paddingBottom="12dp" android:baselineAligned="false">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:orientation="vertical" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="16dp" android:paddingBottom="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_call_log" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
<com.google.android.material.button.MaterialButton android:id="@+id/flush_now_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@string/btn_flush_now" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" style="@style/Widget.EV.Button.Outlined" />
|
||||
<TextView android:id="@+id/call_log_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:minHeight="180dp" android:text="@string/call_log_empty" android:textAppearance="@style/Text.EV.Body" android:fontFamily="monospace" android:textSize="17sp" android:lineSpacingExtra="12dp" android:textIsSelectable="true" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="16dp" android:paddingBottom="16dp" android:background="@drawable/bg_glass">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical">
|
||||
<TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="@string/foreground_title" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
<com.google.android.material.button.MaterialButton android:id="@+id/foreground_refresh_button" style="@style/Widget.EV.Button.Outlined" android:layout_width="48dp" android:layout_height="48dp" android:minWidth="0dp" android:padding="0dp" android:contentDescription="@string/btn_refresh" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" app:iconPadding="0dp" app:iconTint="?attr/evAccent" />
|
||||
</LinearLayout>
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@string/foreground_hint" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/foreground_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:textAppearance="@style/Text.EV.Body" android:fontFamily="monospace" android:textSize="14sp" android:lineSpacingExtra="2dp" android:textIsSelectable="true" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
@@ -1,18 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.evsuite.abrp.StableScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:clipToPadding="false">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="12dp" android:paddingBottom="12dp">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="16dp" android:paddingBottom="16dp" android:background="@drawable/bg_glass">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_call_log" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
<com.google.android.material.button.MaterialButton android:id="@+id/flush_now_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@string/btn_flush_now" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" style="@style/Widget.EV.Button.Outlined" />
|
||||
<TextView android:id="@+id/call_log_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:minHeight="180dp" android:text="@string/call_log_empty" android:textAppearance="@style/Text.EV.Body" android:fontFamily="monospace" android:textSize="17sp" android:lineSpacingExtra="12dp" android:textIsSelectable="true" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass" android:layout_marginTop="16dp">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical">
|
||||
<TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="@string/foreground_title" android:textAppearance="@style/Text.EV.Eyebrow" />
|
||||
<com.google.android.material.button.MaterialButton android:id="@+id/foreground_refresh_button" style="@style/Widget.EV.Button.Outlined" android:layout_width="48dp" android:layout_height="48dp" android:minWidth="0dp" android:padding="0dp" android:contentDescription="@string/btn_refresh" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" app:iconPadding="0dp" app:iconTint="?attr/evAccent" />
|
||||
</LinearLayout>
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@string/foreground_hint" android:textAppearance="@style/Text.EV.Secondary" />
|
||||
<TextView android:id="@+id/foreground_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:textAppearance="@style/Text.EV.Body" android:fontFamily="monospace" android:textSize="14sp" android:lineSpacingExtra="2dp" android:textIsSelectable="true" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</com.evsuite.abrp.StableScrollView>
|
||||
|
||||
@@ -169,15 +169,6 @@
|
||||
<string name="call_log_empty">No upload attempted yet.</string>
|
||||
<string name="update_log_title">Updates, newest first:</string>
|
||||
<string name="btn_flush_now">Upload now</string>
|
||||
<string name="foreground_title">Foreground apps</string>
|
||||
<string name="foreground_hint">Which app was in front and when, from Android\'s own record. Reads only.</string>
|
||||
<string name="foreground_recent">Last 30 minutes, newest first:</string>
|
||||
<string name="foreground_none">No app came to the front in the last 30 minutes.</string>
|
||||
<string name="foreground_unavailable">Not readable in this build: only the car build (system app) may see other apps.</string>
|
||||
<string name="foreground_tasks">Open tasks now, top first:</string>
|
||||
<string name="foreground_policy">policy_control: %1$s</string>
|
||||
<string name="foreground_policy_unset">not set</string>
|
||||
<string name="foreground_status_bar">Status bar height: %1$d px</string>
|
||||
<string name="status_starting">Starting…</string>
|
||||
<string name="status_running_no_upload">Running — waiting for first upload</string>
|
||||
<string name="status_last_upload">Last upload: %1$s (%2$s)</string>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/** The Logs tab's foreground-app lines — pure JVM. */
|
||||
public class ForegroundHistoryTest {
|
||||
|
||||
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
|
||||
|
||||
private static ForegroundHistory.Event at(long seconds, String pkg, String cls) {
|
||||
return new ForegroundHistory.Event(seconds * 1000, pkg, cls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newestFirstWithTheActivityShortened() {
|
||||
List<String> lines = ForegroundHistory.lines(Arrays.asList(
|
||||
at(3600, "com.saic.launcher", "com.saic.launcher.Home"),
|
||||
at(3700, "com.example.carplay", "com.example.carplay.ui.ProjectionActivity")),
|
||||
10, UTC);
|
||||
assertEquals(Arrays.asList(
|
||||
"01:01:40 com.example.carplay .ui.ProjectionActivity",
|
||||
"01:00:00 com.saic.launcher .Home"), lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repeatsOfTheSameScreenCollapse() {
|
||||
List<String> lines = ForegroundHistory.lines(Arrays.asList(
|
||||
at(1, "a.b", "a.b.Main"),
|
||||
at(2, "a.b", "a.b.Main"),
|
||||
at(3, "a.b", "a.b.Other"),
|
||||
at(4, "c.d", null),
|
||||
at(5, "c.d", null)), 10, UTC);
|
||||
assertEquals(Arrays.asList(
|
||||
"00:00:04 c.d",
|
||||
"00:00:03 a.b .Other",
|
||||
"00:00:01 a.b .Main"), lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void limitKeepsTheNewest() {
|
||||
List<String> lines = ForegroundHistory.lines(Arrays.asList(
|
||||
at(1, "one", null), at(2, "two", null), at(3, "three", null)), 2, UTC);
|
||||
assertEquals(Arrays.asList("00:00:03 three", "00:00:02 two"), lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyAndBlankEventsGiveNoLines() {
|
||||
assertTrue(ForegroundHistory.lines(Collections.emptyList(), 10, UTC).isEmpty());
|
||||
assertTrue(ForegroundHistory.lines(Arrays.asList(null, at(1, "", null), at(2, null, "x")), 10, UTC).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aClassOutsideItsPackageStaysWhole() {
|
||||
assertEquals("com.other.Activity", ForegroundHistory.shortClass("a.b", "com.other.Activity"));
|
||||
assertEquals(".Main", ForegroundHistory.shortClass("a.b", "a.b.Main"));
|
||||
assertNull(ForegroundHistory.shortClass("a.b", null));
|
||||
// "a.bc.Main" is not inside "a.b".
|
||||
assertEquals("a.bc.Main", ForegroundHistory.shortClass("a.b", "a.bc.Main"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user