• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2022 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {Engine} from '../common/engine';
16import {
17  TrackControllerFactory,
18  trackControllerRegistry,
19} from '../controller/track_controller';
20import {TrackCreator} from '../frontend/track';
21import {trackRegistry} from '../frontend/track_registry';
22
23import {
24  PluginContext,
25  PluginInfo,
26  TrackInfo,
27  TrackProvider,
28} from './plugin_api';
29import {Registry} from './registry';
30import {Selection} from './state';
31
32// Every plugin gets its own PluginContext. This is how we keep track
33// what each plugin is doing and how we can blame issues on particular
34// plugins.
35export class PluginContextImpl implements PluginContext {
36  readonly pluginId: string;
37  onDetailsPanelSelectionChange?: (newSelection?: Selection) => void;
38  private trackProviders: TrackProvider[];
39
40  constructor(pluginId: string) {
41    this.pluginId = pluginId;
42    this.trackProviders = [];
43  }
44
45  // ==================================================================
46  // The plugin facing API of PluginContext:
47  registerTrackController(track: TrackControllerFactory): void {
48    trackControllerRegistry.register(track);
49  }
50
51  registerTrack(track: TrackCreator): void {
52    trackRegistry.register(track);
53  }
54
55  registerTrackProvider(provider: TrackProvider) {
56    this.trackProviders.push(provider);
57  }
58
59  registerOnDetailsPanelSelectionChange(
60      onDetailsPanelSelectionChange: (newSelection?: Selection) => void) {
61    this.onDetailsPanelSelectionChange = onDetailsPanelSelectionChange;
62  }
63  // ==================================================================
64
65  // ==================================================================
66  // Internal facing API:
67  findPotentialTracks(engine: Engine): Promise<TrackInfo[]>[] {
68    const proxy = engine.getProxy(this.pluginId);
69    return this.trackProviders.map((f) => f(proxy));
70  }
71
72  // Unload the plugin. Ideally no plugin code runs after this point.
73  // PluginContext should unregister everything.
74  revoke() {
75    // TODO(hjd): Remove from trackControllerRegistry, trackRegistry,
76    // etc.
77  }
78  // ==================================================================
79}
80
81// 'Static' registry of all known plugins.
82export class PluginRegistry extends Registry<PluginInfo> {
83  constructor() {
84    super((info) => info.pluginId);
85  }
86}
87
88export class PluginManager {
89  private registry: PluginRegistry;
90  private contexts: Map<string, PluginContextImpl>;
91
92  constructor(registry: PluginRegistry) {
93    this.registry = registry;
94    this.contexts = new Map();
95  }
96
97  activatePlugin(pluginId: string): void {
98    if (this.isActive(pluginId)) {
99      return;
100    }
101    const pluginInfo = this.registry.get(pluginId);
102    const context = new PluginContextImpl(pluginId);
103    this.contexts.set(pluginId, context);
104    pluginInfo.activate(context);
105  }
106
107  deactivatePlugin(pluginId: string): void {
108    const context = this.getPluginContext(pluginId);
109    if (context === undefined) {
110      return;
111    }
112    context.revoke();
113    this.contexts.delete(pluginId);
114  }
115
116  isActive(pluginId: string): boolean {
117    return this.getPluginContext(pluginId) !== undefined;
118  }
119
120  getPluginContext(pluginId: string): PluginContextImpl|undefined {
121    return this.contexts.get(pluginId);
122  }
123
124  findPotentialTracks(engine: Engine): Promise<TrackInfo[]>[] {
125    const promises = [];
126    for (const context of this.contexts.values()) {
127      for (const promise of context.findPotentialTracks(engine)) {
128        promises.push(promise);
129      }
130    }
131    return promises;
132  }
133
134  onDetailsPanelSelectionChange(pluginId: string, newSelection?: Selection) {
135    const pluginContext = this.getPluginContext(pluginId);
136    if (pluginContext === undefined) return;
137    if (pluginContext.onDetailsPanelSelectionChange) {
138      pluginContext.onDetailsPanelSelectionChange(newSelection);
139    }
140  }
141}
142
143// TODO(hjd): Sort out the story for global singletons like these:
144export const pluginRegistry = new PluginRegistry();
145export const pluginManager = new PluginManager(pluginRegistry);
146