* Copyright (c) 2022-2024 Huawei Device Co., Ltd.
* 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 { BaseCloseAppHandler } from '../base/BaseCloseAppHandler';
import {
CheckEmptyUtils,
LogDomain,
Logger,
PixelMapUtil,
StartType,
Trace,
} from '@ohos/basicutils';
import { IconResourceManager, GraphicUtils, onLineThemeUtil, DeviceHelper } from '@ohos/frameworkwrapper';
import { ObjectCopyUtil } from '@ohos/componenthelper';
import type { RectInfo } from '@ohos/basicutils';
import type { AppItemInfo } from '../bean/AppItemInfo';
import { CommonConstants } from '../constants/CommonConstants';
import { image } from '@kit.ImageKit';
import { AppModel, IconChangeListener } from '../model/AppModel';
import { FolderManager } from '../TsIndex';
import { SCBTransitionManager } from '@ohos/windowscene';
import { NumberConstants } from '@ohos/commonconstants';
import { ArrayList } from '@kit.ArkTS';
const TAG = 'CloseAppManager';
const log: Logger = Logger.getLogHelper(LogDomain.ICON);
const widthChangeCardPrefixList: string[] = ['Intelligent_', 'CardsView_CardFrame_', 'Browser_Feed_Default', 'Form'];
const MAX_COUNT: number = 8;
export interface ClosePosition {
appIconSize: number,
appIconHeight: number,
appIconPositionX: number,
appIconPositionY: number,
};
export interface AppInfo {
iconInfo?: ClosePosition,
appItemInfo?: AppItemInfo,
};
class ReleaseSnapshot {
private releaseSnapshot: number;
private releaseFunction?: () => void;
constructor(releaseFunction:() => void, timeout: number) {
this.releaseFunction = releaseFunction;
this.releaseSnapshot = setTimeout(() => {
this.releaseFunction
}, timeout);
}
public executeImmediately(): void {
this.executeOrCancel(true);
}
private executeOrCancel(isExecute: boolean): void {
if (this.releaseSnapshot === CommonConstants.INVALID_VALUE) {
return;
}
try {
clearTimeout(this.releaseSnapshot);
this.releaseSnapshot = CommonConstants.INVALID_VALUE;
if (isExecute && this.releaseFunction) {
this.releaseFunction();
}
this.releaseFunction = undefined;
} catch (e) {
log.showError(TAG, `executeImmediately errork, ${e}`);
}
}
public cancelTimeout(): void {
this.executeOrCancel(false);
}
}
* close app manager
*/
export class CloseAppManager {
private static mInstance: CloseAppManager;
private mBaseCloseAppHandlerList: Array<BaseCloseAppHandler> = new Array<BaseCloseAppHandler>();
private mPagedesktopCloseItemInfo?: AppItemInfo;
private mPagedesktopClosePosition: ClosePosition = { appIconSize: 0,
appIconHeight: 0,
appIconPositionX: 0,
appIconPositionY: 0 };
private mSmartdockCloseItemInfo?: AppItemInfo;
private mSmartdockClosePosition: ClosePosition = { appIconSize: 0,
appIconHeight: 0,
appIconPositionX: 0,
appIconPositionY: 0 };
private mOpenFolderClosePosition: ClosePosition = { appIconSize: 0,
appIconHeight: 0,
appIconPositionX: 0,
appIconPositionY: 0 };
private mStartSubtype: StartSubtype = StartSubtype.NONE;
private mIsStartCard: boolean = false;
private mIsStartShortCut: boolean = false;
private adaptiveIcon: image.PixelMap[] = [];
private cardStartSnapshot?: image.PixelMap;
private releaseSnapshot: ReleaseSnapshot | null = null;
private touchedCardId?: string;
private touchedIconId: number = 0;
private touchedBundleName: string = '';
private touchedModuleName: string = '';
private touchedAbilityName: string = '';
private isUpdatingAdaptiveIcon: boolean = false;
private readonly iconChangeListener: IconChangeListener = { onIconResourceChange: () => this.onIconResourceChange() };
private isMinimizeAllScene: boolean = false;
private isMinimizeAllSceneClosingFolder: boolean = false;
private readonly cardBundleBlocklist: string[] = ['com.ohos.sceneboard'];
constructor() {
AppModel.getInstance().registerIconChangeListener(this.iconChangeListener);
AdaptiveIconManager.getInstance().setDeleteIconCallback((bundleName: string) => {
if (this.touchedBundleName === bundleName) {
this.clearAdaptiveIconCache();
}
});
}
private async onIconResourceChange(): Promise<void> {
this.clearAdaptiveIconCache();
}
static getInstance(): CloseAppManager {
if (!CloseAppManager.mInstance) {
CloseAppManager.mInstance = new CloseAppManager();
}
return CloseAppManager.mInstance;
}
* register baseCloseAppHandler to manager
*
* @param baseCloseAppHandler the instance of close app handler
*/
public registerCloseAppHandler(baseCloseAppHandler: BaseCloseAppHandler): void {
if (CheckEmptyUtils.isEmpty(baseCloseAppHandler)) {
log.showError(TAG, 'registerCloseAppHandler with invalid baseCloseAppHandler');
return;
}
this.mBaseCloseAppHandlerList.push(baseCloseAppHandler);
log.showInfo(TAG, `registerCloseAppHandler mBaseCloseAppHandlerList is ${this.mBaseCloseAppHandlerList.length}} `);
}
* unregister baseCloseAppHandler to manager
*
* @param baseCloseAppHandler the instance of close app handler
*/
public unregisterCloseAppHandler(baseCloseAppHandler: BaseCloseAppHandler): void {
if (CheckEmptyUtils.isEmpty(baseCloseAppHandler)) {
log.showError(TAG, 'unregisterCloseAppHandler with invalid baseCloseAppHandler');
return;
}
let index: number = 0;
for (let i = 0; i < this.mBaseCloseAppHandlerList.length; i++) {
if (this.mBaseCloseAppHandlerList[i] === baseCloseAppHandler) {
index = i;
break;
}
}
this.mBaseCloseAppHandlerList.splice(index, 1);
log.showInfo(TAG, `unregisterCloseAppHandler mBaseCloseAppHandlerList is ${this.mBaseCloseAppHandlerList.length}`);
}
* get app icon info
*
* @param windowTarget windowTarget close window target
*/
public getAppIconInfo(windowTarget: AppItemInfo): void {
if (CheckEmptyUtils.isEmptyArr(this.mBaseCloseAppHandlerList)) {
log.showError(TAG, 'getAppIconInfo with invalid mBaseCloseAppHandlerList');
return;
}
for (let i = 0; i < this.mBaseCloseAppHandlerList.length; i++) {
this.mBaseCloseAppHandlerList[i].getAppIconInfo(windowTarget);
}
}
* get app icon info
*
* @param windowTarget windowTarget close window target
*/
public getAppInfo(windowTarget: AppItemInfo): AppInfo {
if (CheckEmptyUtils.isEmptyArr(this.mBaseCloseAppHandlerList)) {
log.showError(TAG, 'getAppIconInfo with invalid mBaseCloseAppHandlerList');
return {};
}
for (let i = 0; i < this.mBaseCloseAppHandlerList.length; i++) {
this.mBaseCloseAppHandlerList[i].getAppIconInfo(windowTarget);
}
return {iconInfo: this.getAppCloseIconInfo(), appItemInfo: this.getAppCloseItemInfo()};
}
public getAppInfoByBundleName(bundleName: string, abilityName?: string, iconId?: number): AppInfo {
if (CheckEmptyUtils.isEmptyArr(this.mBaseCloseAppHandlerList)) {
log.showError(TAG, 'getAppInfoByBundleName with invalid mBaseCloseAppHandlerList');
return {};
}
for (let i = 0; i < this.mBaseCloseAppHandlerList.length; i++) {
this.mBaseCloseAppHandlerList[i].getAppIconInfoByName(bundleName, abilityName, iconId);
}
return {iconInfo: this.getAppCloseIconInfo(), appItemInfo: this.getAppCloseItemInfo()};
}
public addPagedesktopClosePosition(pagedesktopCloseIconInfo: ClosePosition, pagedesktopCloseItemInfo?: AppItemInfo): void {
log.showDebug(TAG, `addPagedesktopClosePosition pagedesktopCloasIconInfo is ${JSON.stringify(pagedesktopCloseIconInfo)}`);
this.mPagedesktopClosePosition = pagedesktopCloseIconInfo;
this.mPagedesktopCloseItemInfo = pagedesktopCloseItemInfo;
}
public addSmartDockClosePosition(smartdockCloseIconInfo: ClosePosition, smartdockCloseItemInfo: AppItemInfo): void {
log.showDebug(TAG, `addSmartDockClosePosition smartdockCloasIconInfo is ${JSON.stringify(smartdockCloseIconInfo)}`);
this.mSmartdockClosePosition = smartdockCloseIconInfo;
this.mSmartdockCloseItemInfo = smartdockCloseItemInfo;
}
public getAppCloseIconInfo(): ClosePosition {
if (CheckEmptyUtils.isEmpty(this.mPagedesktopClosePosition)) {
log.showDebug(TAG, `getAppCloseIconInfo return mSmartdockClosePosition is ${JSON.stringify(this.mSmartdockClosePosition)}`);
return this.mSmartdockClosePosition;
} else {
log.showDebug(TAG, `getAppCloseIconInfo return mPagedesktopClosePosition is ${JSON.stringify(this.mPagedesktopClosePosition)}`);
return this.mPagedesktopClosePosition;
}
}
public getAppCloseItemInfo(): AppItemInfo | undefined {
if (CheckEmptyUtils.isEmpty(this.mPagedesktopCloseItemInfo)) {
log.showDebug(TAG, `getAppCloseIconInfo return mSmartdockClosePosition is ${JSON.stringify(this.mSmartdockClosePosition)}`);
return this.mSmartdockCloseItemInfo;
} else {
log.showDebug(TAG, `getAppCloseIconInfo return mPagedesktopClosePosition is ${JSON.stringify(this.mPagedesktopClosePosition)}`);
return this.mPagedesktopCloseItemInfo;
}
}
public setOpenFolderAppIconInfo(rectInfo: RectInfo): void {
if (rectInfo === null || rectInfo === undefined) {
log.showWarn(TAG, 'setOpenFolderAppIconInfo rectInfo is empty');
return;
}
if (this.getStartAppType() !== StartType.FOLDER) {
log.showWarn(TAG, 'setOpenFolderAppIconInfo StartType is not FOLDER');
return;
}
this.mOpenFolderClosePosition.appIconSize = rectInfo.right - rectInfo.left;
this.mOpenFolderClosePosition.appIconHeight = rectInfo.bottom - rectInfo.top;
this.mOpenFolderClosePosition.appIconPositionX = rectInfo.left;
this.mOpenFolderClosePosition.appIconPositionY = rectInfo.top;
}
public getOpenFolderAppIconInfo(): ClosePosition | null {
if (this.getStartAppType() !== StartType.FOLDER) {
log.showWarn(TAG, 'getOpenFolderAppIconInfo StartType is not FOLDER');
return null;
}
return this.mOpenFolderClosePosition;
}
public setOpenFolderIconPosition(iconId: string, iconRect: RectInfo): void {
if (this.isAppIconInOpenFolder(iconId)) {
log.showDebug(TAG, 'setOpenFolderIconPosition');
CloseAppManager.getInstance().setOpenFolderAppIconInfo(iconRect);
}
}
public isAppIconInOpenFolder(iconId: string): boolean {
const isOpenFolder: boolean = FolderManager.getInstance().isFolderOpen();
let isStartFromOpenFolder = iconId?.startsWith(CommonConstants.APP_ITEM_APP_BUBBLE_ICON_TAG);
log.showInfo(TAG, `isAppIconInFolderOpen isOpenFolder: ${isOpenFolder} isStartFromOpenFolder: ${isStartFromOpenFolder}`);
return isStartFromOpenFolder && isOpenFolder;
}
public setStartAppType(startAppType: StartType, cardId?: string, extraId?: string, shortcutId?: string,
bundleName?: string): void {
if (startAppType === StartType.CARD && !this.cardBundleBlocklist.includes(bundleName)) {
this.setIsStartCard();
} else if (startAppType === StartType.SHORTCUT_MENU || startAppType === StartType.SHORTCUT_APP) {
this.setIsStartShortcut();
} else {
this.resetIsStartCard();
this.resetIsStartShortcut();
}
this.mBaseCloseAppHandlerList.forEach(handler => {
handler?.setStartAppType(startAppType, cardId, extraId, shortcutId);
});
}
public getIsMinimizeAllScene(): boolean {
return this.isMinimizeAllScene;
}
public setIsMinimizeAllScene(value: boolean): void {
this.isMinimizeAllScene = value;
}
public getIsMinimizeAllSceneClosingFolder(): boolean {
return this.isMinimizeAllSceneClosingFolder;
}
public resetIsMinimizeAllSceneClosingFolder(): void {
this.isMinimizeAllSceneClosingFolder = false;
}
public setIsMinimizeAllSceneClosingFolder(): void {
this.isMinimizeAllSceneClosingFolder = true;
}
public getStartAppType(): StartType {
return this.mBaseCloseAppHandlerList[0]?.mStartAppType;
}
public getStartCardId(): string {
return this.mBaseCloseAppHandlerList[0]?.mCardId;
}
public getExtraId(): string {
return this.mBaseCloseAppHandlerList[0]?.mExtraId;
}
public getShortcutId(): string {
return this.mBaseCloseAppHandlerList[0]?.mShortcutId;
}
public setStartAppSubtype(type: StartSubtype): void {
log.showInfo(TAG, `set start app subtype, type=${type}`);
this.mStartSubtype = type;
}
public getStartAppSubtype(): number {
return this.mStartSubtype;
}
public resetStartAppSubtype(): void {
log.showInfo(TAG, 'reset start app subtype');
this.mStartSubtype = StartSubtype.NONE;
}
public setIsStartCard(): void {
this.mIsStartCard = true;
}
public getAndResetIsStartCard(): boolean {
let tempIsStartCard: boolean = this.mIsStartCard;
this.resetIsStartCard();
return tempIsStartCard;
}
public resetIsStartCard(): void {
this.mIsStartCard = false;
}
public setIsStartShortcut(): void {
this.mIsStartShortCut = true;
}
public resetIsStartShortcut(): void {
this.mIsStartShortCut = false;
}
public getAndResetIsStartShortcut(): boolean {
let tempIsStartShortCut: boolean = this.mIsStartShortCut;
this.resetIsStartShortcut();
return tempIsStartShortCut;
}
public clearAdaptiveIconCache(iconId?: number): void {
if (iconId && iconId !== this.touchedIconId) {
log.showError(TAG, `clearAdaptiveIconCache ${iconId} is not touched icon`);
return;
}
this.touchedBundleName = '';
this.touchedModuleName = '';
this.touchedAbilityName = '';
this.touchedIconId = 0;
const tempArray : ArrayList<image.PixelMap> = new ArrayList();
for (let i = 0; i < this.adaptiveIcon.length; i++) {
const adaptiveIconItemIsEmpty: boolean = CheckEmptyUtils.isEmpty(this.adaptiveIcon[i]);
log.showWarn(TAG, `clearAdaptiveIconCache item${i} is empty: ${adaptiveIconItemIsEmpty}`);
if (!adaptiveIconItemIsEmpty) {
tempArray.add(this.adaptiveIcon[i]);
}
}
this.adaptiveIcon = [];
if (tempArray.length === 0) {
return;
}
if (!DeviceHelper.isPad()) {
return;
}
if (SCBTransitionManager.getInstance().getStartExitAnimationCount() > 0) {
this.setOrResetReleaseSnapshot(() => {
log.showWarn(TAG, `clearAdaptiveIconCache timeout`);
tempArray.forEach((item: image.PixelMap) => {
item.release();
});
}, NumberConstants.CONSTANT_NUMBER_ONE_THOUSAND);
} else {
log.showWarn(TAG, `clearAdaptiveIconCache`);
tempArray.forEach((item: image.PixelMap) => {
item.release();
});
}
}
public async setAdaptiveIcon(bundleName: string, moduleName: string, abilityName: string, iconId: number): Promise<void> {
if (this.isUpdatingAdaptiveIcon) {
log.showWarn(TAG, `setAdaptiveIcon: ${bundleName} isUpdateAdaptiveIcon return`);
return;
}
if (onLineThemeUtil.isOnlineTheme()) {
log.showWarn(TAG, 'Adaptive icons in online themes are not supported');
return;
}
Trace.start(`setAdaptiveIcon, bundleName: ${bundleName}`);
log.showWarn(TAG, `setAdaptiveIcon, bundleName: ${bundleName} iconId: ${iconId} start`);
this.isUpdatingAdaptiveIcon = true;
this.clearAdaptiveIconCache();
const iconResources: image.PixelMap[] =
await AdaptiveIconManager.getInstance().getIconResource(bundleName, moduleName, abilityName);
if (iconResources.length !== 0) {
this.adaptiveIcon[0] = iconResources[0];
this.adaptiveIcon[1] = iconResources[1];
this.touchedBundleName = bundleName;
this.touchedModuleName = moduleName;
this.touchedAbilityName = abilityName;
this.touchedIconId = iconId;
}
this.isUpdatingAdaptiveIcon = false;
log.showWarn(TAG, `setAdaptiveIcon, bundleName: ${bundleName} end`);
Trace.end(`setAdaptiveIcon, bundleName: ${bundleName}`);
}
public async getAdaptiveIcon(bundleName: string, moduleName: string, abilityName: string, touchedIconId: number):
Promise<(image.PixelMap)[]> {
if (onLineThemeUtil.isOnlineTheme()) {
log.showInfo(TAG, `getAdaptiveIcon, Adaptive icons in online themes are not supported`);
return [];
}
if (!this.isUpdatingAdaptiveIcon && this.touchedBundleName === bundleName && this.touchedModuleName === moduleName &&
this.touchedAbilityName === abilityName && this.touchedIconId === touchedIconId) {
return this.adaptiveIcon;
} else if (!this.isUpdatingAdaptiveIcon) {
await this.setAdaptiveIcon(bundleName, moduleName, abilityName, touchedIconId);
return this.adaptiveIcon;
}
return [];
}
* 保存卡片截图,用于启动退出动效
* @param cardId 卡片id
* @param image 截图
*/
public setCardStartSnapshot(cardId: string, image: image.PixelMap): void {
this.touchedCardId = cardId;
let imageTmp = this.cardStartSnapshot;
try {
this.cardStartSnapshot = ObjectCopyUtil.deepCopyPixelMap(image);
} catch (err) {
log.showError(TAG, `deepCopyPixelMap failed: ${err?.msg}, using reference!`);
this.cardStartSnapshot = image;
}
if (imageTmp) {
if (SCBTransitionManager.getInstance().getStartExitAnimationCount() > 0) {
this.setOrResetReleaseSnapshot(() => {imageTmp?.release()},
NumberConstants.CONSTANT_NUMBER_ONE_THOUSAND);
} else {
imageTmp.release();
}
}
log.showInfo(TAG, `setCardStartSnapshot: ${cardId}`);
}
private setOrResetReleaseSnapshot(releaseFunction:() => void, timeout: number): void {
if (this.releaseSnapshot) {
this.releaseSnapshot.executeImmediately();
this.releaseSnapshot = null;
}
this.releaseSnapshot = new ReleaseSnapshot(releaseFunction, timeout);
}
* 获取卡片截图
* @param cardId 卡片id
* @returns 截图pixelMap
*/
public getCardStartSnapshot(cardId: string): image.PixelMap | undefined {
if (cardId !== this.touchedCardId) {
log.showError(TAG, `${cardId} is not touched card`);
return undefined;
}
return this.cardStartSnapshot;
}
* 删除卡片缓存的截图
*/
public deleteCardCache(cardId?: string): void {
if (cardId && cardId !== this.touchedCardId) {
log.showError(TAG, `deleteCardCache ${cardId} is not touched card`);
return;
}
if (this.cardStartSnapshot) {
let cacheCardShot: image.PixelMap = this.cardStartSnapshot;
cacheCardShot.release();
this.cardStartSnapshot = undefined;
}
}
* 根据id来判断卡片或图标退出时宽度是否发生改变
* @param id 图标id
* @returns 图标id存在且卡片宽度发生改变则返回true,否则false
*/
public isWidthChangeCard(id: string): boolean {
return id ? widthChangeCardPrefixList.some((item) => id.startsWith(item)) : false;
}
}
export enum StartSubtype {
NONE,
DOCK_ICON,
DOCK_RIGHT_MENU,
ICON_FROM_APP_CENTER,
ICON_FROM_OTHER
}
* 应用退出动效图标缓存管理类
*/
export class AdaptiveIconManager {
private static instance?: AdaptiveIconManager;
private iconResources: IconResource[] = [];
private deleteIconCallback?: (bundleName: string) => void;
* 获取单例类
*/
public static getInstance(): AdaptiveIconManager {
if (!AdaptiveIconManager.instance) {
AdaptiveIconManager.instance = new AdaptiveIconManager();
}
return AdaptiveIconManager.instance;
}
* 获取指定标识的图标缓存
*
* @param key 指定图标标识
*/
public async getIconResource(bundleName: string, moduleName: string, abilityName: string): Promise<image.PixelMap[]> {
const index: number = this.iconResources.findIndex((value: IconResource) => {
return value.bundleName === bundleName && value.moduleName === moduleName && value.abilityName === abilityName;
});
if (index >= 0) {
const resoure: IconResource = this.iconResources[index];
this.iconResources.splice(index, 1);
this.iconResources.unshift(resoure);
log.showInfo(TAG, 'get icon resource, bundleName: %{public}s', bundleName);
return [resoure.backgroundImage, resoure.foregroundImage];
}
let iconInfo = await IconResourceManager.getInstance().getIconResource(bundleName, moduleName, abilityName);
if (!CheckEmptyUtils.isEmpty(iconInfo.adaptivePic[0])) {
let backgroundImage: image.PixelMap = await GraphicUtils.changeBase64ToPixel(iconInfo.adaptivePic[0]);
let foregroundImage: image.PixelMap = await GraphicUtils.changeBase64ToPixel(iconInfo.adaptivePic[1]);
PixelMapUtil.addName(backgroundImage, `Adaptive_background_${bundleName}`);
PixelMapUtil.addName(foregroundImage, `Adaptive_foreground_${bundleName}`);
this.putIconResource(new IconResource(bundleName, moduleName, abilityName, backgroundImage, foregroundImage));
return [backgroundImage, foregroundImage];
}
return [];
}
* 添加图标缓存
*
* @param key 缓存标识
* @param images 图标内容
*/
public putIconResource(resoure: IconResource): void {
if (DeviceHelper.isPad()) {
return;
}
if (this.iconResources.length >= MAX_COUNT) {
const removeResource: IconResource = this.iconResources.pop();
removeResource.release();
log.showInfo(TAG, 'remove icon resources, bundleName: %{public}s', removeResource.bundleName);
}
this.iconResources.unshift(resoure);
log.showInfo(TAG, 'put icon resource, bundleName: %{public}s, size: %{public}d', resoure.bundleName, this.iconResources.length);
}
* 删除指定包名的图标缓存
*
* @param bundleName 应用包名
*/
public deleteIconResource(bundleName: string): void {
this.iconResources.filter((value: IconResource) => {
return value.bundleName === bundleName;
}).forEach((value: IconResource) => {
value.release();
});
this.iconResources = this.iconResources.filter((value: IconResource) => {
return value.bundleName !== bundleName;
});
this.deleteIconCallback?.(bundleName);
log.showInfo(TAG, 'delete icon resource, bundleName: %{public}s, size: %{public}d', bundleName, this.iconResources.length);
}
* 清空所有图标缓存
*
* @param msg 来源
*/
public clearIconResources(msg: string): void {
this.iconResources.forEach((resource: IconResource) => {
resource.release();
this.deleteIconCallback?.(resource.bundleName);
});
this.iconResources = [];
log.showInfo(TAG, 'clear icon resources, by %{public}s', msg);
}
* 设置删除图标的回调
*
* @param callback 删除图标的回调
*/
public setDeleteIconCallback(callback: (bundleName: string) => void): void {
this.deleteIconCallback = callback;
}
}
export class IconResource {
public bundleName: string;
public moduleName: string;
public abilityName: string;
public backgroundImage: image.PixelMap;
public foregroundImage: image.PixelMap;
constructor(bundleName: string, moduleName: string, abilityName: string,
backgroundImage: image.PixelMap, foregroundImage: image.PixelMap) {
this.bundleName = bundleName;
this.moduleName = moduleName;
this.abilityName = abilityName;
this.backgroundImage = backgroundImage;
this.foregroundImage = foregroundImage;
}
public release(): void {
this.backgroundImage.release();
this.foregroundImage.release();
}
}