Presenter/Mediator changes to save presets.
Only for hierarchy based traces at the moment.
Screencast:https://screencast.googleplex.com/cast/NDY3MjEyMzU4NTk1Mzc5Mnw5ZjliNjQ2MC0zZA
Bug: 291222624
Test: npm run test:unit:ci
Change-Id: I4eafb48dcd99921db3c5b6f2a9ca75370405d92b
diff --git a/tools/winscope/src/app/mediator.ts b/tools/winscope/src/app/mediator.ts
index d39ad4b..33608fd 100644
--- a/tools/winscope/src/app/mediator.ts
+++ b/tools/winscope/src/app/mediator.ts
@@ -45,6 +45,7 @@
import {TraceEntry} from 'trace/trace';
import {TRACE_INFO} from 'trace/trace_info';
import {TracePosition} from 'trace/trace_position';
+import {TraceType} from 'trace/trace_type';
import {RequestedTraceTypes} from 'trace_collection/adb_files';
import {View, Viewer, ViewType} from 'viewers/viewer';
import {ViewerFactory} from 'viewers/viewer_factory';
@@ -275,6 +276,20 @@
UserNotifier.add(new NoTraceTargetsSelected()).notify();
},
);
+
+ await event.visit(
+ WinscopeEventType.FILTER_PRESET_SAVE_REQUEST,
+ async (event) => {
+ await this.findViewerByType(event.traceType)?.onWinscopeEvent(event);
+ },
+ );
+
+ await event.visit(
+ WinscopeEventType.FILTER_PRESET_APPLY_REQUEST,
+ async (event) => {
+ await this.findViewerByType(event.traceType)?.onWinscopeEvent(event);
+ },
+ );
}
private async loadFiles(files: File[], source: FilesSource) {
@@ -519,4 +534,8 @@
await overlay.onWinscopeEvent(event);
}
}
+
+ private findViewerByType(type: TraceType): Viewer | undefined {
+ return this.viewers.find((viewer) => viewer.getTraces()[0].type === type);
+ }
}
diff --git a/tools/winscope/src/common/persistent_store_proxy.ts b/tools/winscope/src/common/persistent_store_proxy.ts
index 7c67552..af7087c 100644
--- a/tools/winscope/src/common/persistent_store_proxy.ts
+++ b/tools/winscope/src/common/persistent_store_proxy.ts
@@ -22,7 +22,7 @@
defaultState: T,
storage: Store,
): T {
- const storedState = JSON.parse(storage.get(key) ?? '{}');
+ const storedState = JSON.parse(storage.get(key) ?? '{}', parseMap);
const currentState = mergeDeep({}, structuredClone(defaultState));
mergeDeepKeepingStructure(currentState, storedState);
return wrapWithPersistentStoreProxy(key, currentState, storage) as T;
@@ -66,7 +66,7 @@
(typeof prop === 'number' || !Number.isNaN(Number(prop)))
) {
target[Number(prop)] = newValue;
- storage.add(storeKey, JSON.stringify(baseObject));
+ storage.add(storeKey, JSON.stringify(baseObject, stringifyMap));
return true;
}
if (!Array.isArray(target) && Array.isArray(newValue)) {
@@ -76,12 +76,12 @@
storage,
baseObject,
);
- storage.add(storeKey, JSON.stringify(baseObject));
+ storage.add(storeKey, JSON.stringify(baseObject, stringifyMap));
return true;
}
if (!Array.isArray(target) && updatableProps.includes(prop)) {
(target as any)[prop] = newValue;
- storage.add(storeKey, JSON.stringify(baseObject));
+ storage.add(storeKey, JSON.stringify(baseObject, stringifyMap));
return true;
}
throw new Error(
@@ -142,3 +142,20 @@
return mergeDeep(target, ...sources);
}
+
+export function stringifyMap(key: string, value: any) {
+ if (value instanceof Map) {
+ return {
+ type: 'Map',
+ value: [...value],
+ };
+ }
+ return value;
+}
+
+export function parseMap(key: string, value: any) {
+ if (value && value.type === 'Map') {
+ return new Map(value.value);
+ }
+ return value;
+}
diff --git a/tools/winscope/src/common/persistent_store_proxy_test.ts b/tools/winscope/src/common/persistent_store_proxy_test.ts
index 986807d..eaeb05d 100644
--- a/tools/winscope/src/common/persistent_store_proxy_test.ts
+++ b/tools/winscope/src/common/persistent_store_proxy_test.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import {InMemoryStorage} from 'common/in_memory_storage';
+import {InMemoryStorage} from './in_memory_storage';
import {PersistentStoreProxy} from './persistent_store_proxy';
describe('PersistentStoreObject', () => {
diff --git a/tools/winscope/src/viewers/common/abstract_hierarchy_viewer_presenter.ts b/tools/winscope/src/viewers/common/abstract_hierarchy_viewer_presenter.ts
index 1acca66..9cabd68 100644
--- a/tools/winscope/src/viewers/common/abstract_hierarchy_viewer_presenter.ts
+++ b/tools/winscope/src/viewers/common/abstract_hierarchy_viewer_presenter.ts
@@ -16,8 +16,13 @@
import {assertDefined} from 'common/assert_utils';
import {FunctionUtils} from 'common/function_utils';
+import {parseMap, stringifyMap} from 'common/persistent_store_proxy';
import {Store} from 'common/store';
-import {TracePositionUpdate, WinscopeEvent} from 'messaging/winscope_event';
+import {
+ TracePositionUpdate,
+ WinscopeEvent,
+ WinscopeEventType,
+} from 'messaging/winscope_event';
import {
EmitEvent,
WinscopeEventEmitter,
@@ -33,6 +38,7 @@
import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node';
import {UserOptions} from 'viewers/common/user_options';
import {HierarchyPresenter} from './hierarchy_presenter';
+import {PresetHierarchy} from './preset_hierarchy';
import {RectShowState} from './rect_show_state';
import {TextFilter} from './text_filter';
import {UiDataHierarchy} from './ui_data_hierarchy';
@@ -207,6 +213,58 @@
this.copyUiDataAndNotifyView();
}
+ protected async handleCommonWinscopeEvents(event: WinscopeEvent) {
+ await event.visit(
+ WinscopeEventType.FILTER_PRESET_SAVE_REQUEST,
+ async (event) => {
+ this.saveConfigAsPreset(event.name);
+ },
+ );
+ }
+
+ protected saveConfigAsPreset(storeKey: string) {
+ const preset: PresetHierarchy = {
+ hierarchyUserOptions: this.uiData.hierarchyUserOptions,
+ hierarchyFilter: this.uiData.hierarchyFilter,
+ propertiesUserOptions: this.uiData.propertiesUserOptions,
+ propertiesFilter: this.uiData.propertiesFilter,
+ rectsUserOptions: this.uiData.rectsUserOptions,
+ rectIdToShowState: this.uiData.rectIdToShowState,
+ };
+ this.storage.add(storeKey, JSON.stringify(preset, stringifyMap));
+ }
+
+ protected async applyPresetConfig(storeKey: string) {
+ const preset = this.storage.get(storeKey);
+ if (preset) {
+ const parsedPreset: PresetHierarchy = JSON.parse(preset, parseMap);
+ await this.hierarchyPresenter.applyHierarchyUserOptionsChange(
+ parsedPreset.hierarchyUserOptions,
+ );
+ await this.hierarchyPresenter.applyHierarchyFilterChange(
+ parsedPreset.hierarchyFilter,
+ );
+
+ this.propertiesPresenter.applyPropertiesUserOptionsChange(
+ parsedPreset.propertiesUserOptions,
+ );
+ this.propertiesPresenter.applyPropertiesFilterChange(
+ parsedPreset.propertiesFilter,
+ );
+ await this.updatePropertiesTree();
+
+ if (this.rectsPresenter) {
+ this.rectsPresenter?.applyRectsUserOptionsChange(
+ assertDefined(parsedPreset.rectsUserOptions),
+ );
+ this.rectsPresenter?.updateRectShowStates(
+ parsedPreset.rectIdToShowState,
+ );
+ }
+ this.refreshHierarchyViewerUiData();
+ }
+ }
+
protected async applyTracePositionUpdate(event: TracePositionUpdate) {
let entries: Array<TraceEntry<HierarchyTreeNode>> = [];
if (this.multiTraceType !== undefined) {
diff --git a/tools/winscope/src/viewers/common/abstract_hierarchy_viewer_presenter_test.ts b/tools/winscope/src/viewers/common/abstract_hierarchy_viewer_presenter_test.ts
index 850bae8..f536c3b 100644
--- a/tools/winscope/src/viewers/common/abstract_hierarchy_viewer_presenter_test.ts
+++ b/tools/winscope/src/viewers/common/abstract_hierarchy_viewer_presenter_test.ts
@@ -16,10 +16,17 @@
import {assertDefined} from 'common/assert_utils';
import {Rect} from 'common/geometry/rect';
-import {TracePositionUpdate} from 'messaging/winscope_event';
+import {InMemoryStorage} from 'common/in_memory_storage';
+import {Store} from 'common/store';
+import {
+ FilterPresetApplyRequest,
+ FilterPresetSaveRequest,
+ TracePositionUpdate,
+} from 'messaging/winscope_event';
import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils';
import {TreeNodeUtils} from 'test/unit/tree_node_utils';
import {UserNotifierChecker} from 'test/unit/user_notifier_checker';
+import {TraceType} from 'trace/trace_type';
import {
AbstractHierarchyViewerPresenter,
NotifyHierarchyViewCallbackType,
@@ -41,6 +48,7 @@
let uiData: UiDataHierarchy;
let presenter: AbstractHierarchyViewerPresenter<UiData>;
let userNotifierChecker: UserNotifierChecker;
+ let storage: InMemoryStorage;
beforeAll(async () => {
jasmine.addCustomEqualityTester(TreeNodeUtils.treeNodeEqualityTester);
@@ -49,9 +57,10 @@
});
beforeEach(() => {
+ storage = new InMemoryStorage();
presenter = this.createPresenter((newData) => {
uiData = newData;
- });
+ }, storage);
});
afterEach(() => {
@@ -593,6 +602,42 @@
});
}
+ it('handles filter preset requests', async () => {
+ await presenter.onAppEvent(this.getPositionUpdate());
+ const saveEvent = new FilterPresetSaveRequest(
+ 'TestPreset',
+ TraceType.TEST_TRACE_STRING,
+ );
+ expect(storage.get(saveEvent.name)).toBeUndefined();
+ await presenter.onAppEvent(saveEvent);
+ expect(storage.get(saveEvent.name)).toBeDefined();
+
+ await presenter.onHierarchyFilterChange(
+ new TextFilter('Test Filter', []),
+ );
+ await presenter.onHierarchyUserOptionsChange({});
+ await presenter.onPropertiesUserOptionsChange({});
+ await presenter.onPropertiesFilterChange(
+ new TextFilter('Test Filter', []),
+ );
+
+ if (this.shouldExecuteRectTests) {
+ presenter.onRectsUserOptionsChange({});
+ await presenter.onRectShowStateChange(
+ assertDefined(uiData.rectsToDraw)[0].id,
+ RectShowState.HIDE,
+ );
+ }
+ const currentUiData = uiData;
+
+ const applyEvent = new FilterPresetApplyRequest(
+ saveEvent.name,
+ TraceType.TEST_TRACE_STRING,
+ );
+ await presenter.onAppEvent(applyEvent);
+ expect(uiData).not.toEqual(currentUiData);
+ });
+
function pinNode(node: UiHierarchyTreeNode) {
presenter.onPinnedItemChange(node);
expect(uiData.pinnedItems).toEqual([node]);
@@ -651,6 +696,7 @@
abstract setUpTestEnvironment(): Promise<void>;
abstract createPresenter(
callback: NotifyHierarchyViewCallbackType<UiData>,
+ storage: Store,
): AbstractHierarchyViewerPresenter<UiData>;
abstract createPresenterWithEmptyTrace(
callback: NotifyHierarchyViewCallbackType<UiData>,
diff --git a/tools/winscope/src/viewers/common/abstract_presenter_input_method.ts b/tools/winscope/src/viewers/common/abstract_presenter_input_method.ts
index 59aac44..caa6503 100644
--- a/tools/winscope/src/viewers/common/abstract_presenter_input_method.ts
+++ b/tools/winscope/src/viewers/common/abstract_presenter_input_method.ts
@@ -136,6 +136,7 @@
}
async onAppEvent(event: WinscopeEvent) {
+ await this.handleCommonWinscopeEvents(event);
await event.visit(
WinscopeEventType.TRACE_POSITION_UPDATE,
async (event) => {
@@ -187,6 +188,14 @@
this.refreshUIData();
},
);
+ await event.visit(
+ WinscopeEventType.FILTER_PRESET_APPLY_REQUEST,
+ async (event) => {
+ const filterPresetName = event.name;
+ await this.applyPresetConfig(filterPresetName);
+ this.refreshUIData();
+ },
+ );
}
async onHighlightedNodeChange(node: UiHierarchyTreeNode) {
diff --git a/tools/winscope/src/viewers/common/abstract_presenter_input_method_test.ts b/tools/winscope/src/viewers/common/abstract_presenter_input_method_test.ts
index 99473aa..94b3d58 100644
--- a/tools/winscope/src/viewers/common/abstract_presenter_input_method_test.ts
+++ b/tools/winscope/src/viewers/common/abstract_presenter_input_method_test.ts
@@ -16,6 +16,7 @@
import {assertDefined} from 'common/assert_utils';
import {InMemoryStorage} from 'common/in_memory_storage';
+import {Store} from 'common/store';
import {TracePositionUpdate} from 'messaging/winscope_event';
import {TraceBuilder} from 'test/unit/trace_builder';
import {TreeNodeUtils} from 'test/unit/tree_node_utils';
@@ -120,15 +121,11 @@
override createPresenter(
callback: NotifyHierarchyViewCallbackType<ImeUiData>,
+ storage: Store,
): AbstractPresenterInputMethod {
const traces = assertDefined(this.traces);
const trace = assertDefined(traces.getTrace(this.imeTraceType));
- return new this.PresenterInputMethod(
- trace,
- traces,
- new InMemoryStorage(),
- callback,
- );
+ return new this.PresenterInputMethod(trace, traces, storage, callback);
}
override getPositionUpdate(): TracePositionUpdate {
diff --git a/tools/winscope/src/viewers/common/preset_hierarchy.ts b/tools/winscope/src/viewers/common/preset_hierarchy.ts
new file mode 100644
index 0000000..6a9bffb
--- /dev/null
+++ b/tools/winscope/src/viewers/common/preset_hierarchy.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+import {UserOptions} from 'viewers/common/user_options';
+import {RectShowState} from './rect_show_state';
+import {TextFilter} from './text_filter';
+
+export interface PresetHierarchy {
+ hierarchyUserOptions: UserOptions;
+ hierarchyFilter: TextFilter;
+ propertiesUserOptions: UserOptions;
+ propertiesFilter: TextFilter;
+ rectsUserOptions?: UserOptions;
+ rectIdToShowState?: Map<string, RectShowState> | undefined;
+}
diff --git a/tools/winscope/src/viewers/viewer_surface_flinger/presenter.ts b/tools/winscope/src/viewers/viewer_surface_flinger/presenter.ts
index 2eed62c..f488825 100644
--- a/tools/winscope/src/viewers/viewer_surface_flinger/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_surface_flinger/presenter.ts
@@ -185,6 +185,7 @@
}
override async onAppEvent(event: WinscopeEvent) {
+ await this.handleCommonWinscopeEvents(event);
await event.visit(
WinscopeEventType.TRACE_POSITION_UPDATE,
async (event) => {
@@ -195,6 +196,15 @@
this.refreshUIData();
},
);
+ await event.visit(
+ WinscopeEventType.FILTER_PRESET_APPLY_REQUEST,
+ async (event) => {
+ const filterPresetName = event.name;
+ await this.applyPresetConfig(filterPresetName);
+ this.updateCuratedProperties();
+ this.refreshUIData();
+ },
+ );
}
override async onHighlightedNodeChange(item: UiHierarchyTreeNode) {
diff --git a/tools/winscope/src/viewers/viewer_surface_flinger/presenter_test.ts b/tools/winscope/src/viewers/viewer_surface_flinger/presenter_test.ts
index 6b4b5ab..4438c75 100644
--- a/tools/winscope/src/viewers/viewer_surface_flinger/presenter_test.ts
+++ b/tools/winscope/src/viewers/viewer_surface_flinger/presenter_test.ts
@@ -17,6 +17,7 @@
import {assertDefined} from 'common/assert_utils';
import {Rect} from 'common/geometry/rect';
import {InMemoryStorage} from 'common/in_memory_storage';
+import {Store} from 'common/store';
import {TracePositionUpdate} from 'messaging/winscope_event';
import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder';
import {TraceBuilder} from 'test/unit/trace_builder';
@@ -143,11 +144,12 @@
override createPresenter(
callback: NotifyHierarchyViewCallbackType<UiData>,
+ storage: Store,
): Presenter {
const traces = new Traces();
const traceSf = assertDefined(this.traceSf);
traces.addTrace(traceSf);
- return new Presenter(traceSf, traces, new InMemoryStorage(), callback);
+ return new Presenter(traceSf, traces, storage, callback);
}
override getPositionUpdate(): TracePositionUpdate {
@@ -286,6 +288,7 @@
};
presenter = this.createPresenter(
notifyViewCallback as NotifyHierarchyViewCallbackType<UiData>,
+ new InMemoryStorage(),
);
});
diff --git a/tools/winscope/src/viewers/viewer_view_capture/presenter.ts b/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
index 6cc56f0..14d30af 100644
--- a/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
@@ -169,6 +169,7 @@
}
override async onAppEvent(event: WinscopeEvent) {
+ await this.handleCommonWinscopeEvents(event);
await event.visit(
WinscopeEventType.TRACE_POSITION_UPDATE,
async (event) => {
@@ -192,6 +193,15 @@
this.refreshUIData();
},
);
+ await event.visit(
+ WinscopeEventType.FILTER_PRESET_APPLY_REQUEST,
+ async (event) => {
+ const filterPresetName = event.name;
+ await this.applyPresetConfig(filterPresetName);
+ this.updateCuratedProperties();
+ this.refreshUIData();
+ },
+ );
}
override async onHighlightedNodeChange(node: UiHierarchyTreeNode) {
diff --git a/tools/winscope/src/viewers/viewer_view_capture/presenter_test.ts b/tools/winscope/src/viewers/viewer_view_capture/presenter_test.ts
index 34b0f53..6420306 100644
--- a/tools/winscope/src/viewers/viewer_view_capture/presenter_test.ts
+++ b/tools/winscope/src/viewers/viewer_view_capture/presenter_test.ts
@@ -17,6 +17,7 @@
import {assertDefined} from 'common/assert_utils';
import {Rect} from 'common/geometry/rect';
import {InMemoryStorage} from 'common/in_memory_storage';
+import {Store} from 'common/store';
import {TracePositionUpdate} from 'messaging/winscope_event';
import {TraceBuilder} from 'test/unit/trace_builder';
import {UnitTestUtils} from 'test/unit/utils';
@@ -122,12 +123,9 @@
override createPresenter(
callback: NotifyHierarchyViewCallbackType<UiData>,
+ storage: Store,
): Presenter {
- return new Presenter(
- assertDefined(this.traces),
- new InMemoryStorage(),
- callback,
- );
+ return new Presenter(assertDefined(this.traces), storage, callback);
}
override getPositionUpdate(): TracePositionUpdate {
diff --git a/tools/winscope/src/viewers/viewer_window_manager/presenter.ts b/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
index dc93169..1ffd4df 100644
--- a/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
@@ -143,6 +143,7 @@
}
override async onAppEvent(event: WinscopeEvent) {
+ await this.handleCommonWinscopeEvents(event);
await event.visit(
WinscopeEventType.TRACE_POSITION_UPDATE,
async (event) => {
@@ -150,6 +151,14 @@
this.refreshUIData();
},
);
+ await event.visit(
+ WinscopeEventType.FILTER_PRESET_APPLY_REQUEST,
+ async (event) => {
+ const filterPresetName = event.name;
+ await this.applyPresetConfig(filterPresetName);
+ this.refreshUIData();
+ },
+ );
}
override async onHighlightedNodeChange(item: UiHierarchyTreeNode) {
diff --git a/tools/winscope/src/viewers/viewer_window_manager/presenter_test.ts b/tools/winscope/src/viewers/viewer_window_manager/presenter_test.ts
index 663d860..1168e18 100644
--- a/tools/winscope/src/viewers/viewer_window_manager/presenter_test.ts
+++ b/tools/winscope/src/viewers/viewer_window_manager/presenter_test.ts
@@ -17,6 +17,7 @@
import {assertDefined} from 'common/assert_utils';
import {Rect} from 'common/geometry/rect';
import {InMemoryStorage} from 'common/in_memory_storage';
+import {Store} from 'common/store';
import {TracePositionUpdate} from 'messaging/winscope_event';
import {TraceBuilder} from 'test/unit/trace_builder';
import {UnitTestUtils} from 'test/unit/utils';
@@ -108,11 +109,12 @@
override createPresenter(
callback: NotifyHierarchyViewCallbackType<UiData>,
+ storage: Store,
): Presenter {
const traces = new Traces();
const trace = assertDefined(this.trace);
traces.addTrace(trace);
- return new Presenter(trace, traces, new InMemoryStorage(), callback);
+ return new Presenter(trace, traces, storage, callback);
}
override getPositionUpdate(): TracePositionUpdate {