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