* Copyright (c) Huawei Device Co., Ltd. 2024-2025. All rights reserved.
* 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 lazy { ZoomAction } from './ZoomAction';
import lazy { HiLog } from '../../utils/HiLog';
import lazy { BaseFunction } from '../core/BaseFunction';
import lazy { FunctionId } from '../core/functionproperty/FunctionId';
import lazy { ValueSet } from '../core/ValueSet';
import lazy { ConflictParam } from '../core/ConflictParam';
import lazy { FlashMode } from '../../function/enumbase/FlashMode';
import lazy { CameraAction, CameraStartType } from '../../camera/uithread/CameraAction';
import lazy { FeatureManager } from '../core/FeatureManager';
import lazy { RenderLocation } from '../core/functionproperty/RenderLocation';
import lazy { RenderType } from '../core/functionproperty/RenderType';
import lazy { UiElement } from '../core/UiElement';
import lazy { ModeType } from '../../mode/ModeType';
import camera from '@ohos.multimedia.camera';
import lazy { ZoomOperation } from './ZoomOperation';
import lazy { CameraAppCapability } from '../../camera/CameraAppCapability';
import lazy { TreasureBoxAction } from '../../component/treasurebox/reduce/TreasureBoxAction';
import lazy { getCloseFlashZoom, isNeedHideFlashIcon } from '../../utils/FileReadUtil';
import lazy { PickerUtils } from '../../utils/PickerUtils';
import lazy { PropTag } from '../../service/preferences/PropTag';
import lazy { PersistType } from '../../service/preferences/PreferencesService';
import lazy { getStates } from '../../redux';
import lazy { OutputSwitcher } from '../outputswitcher/OutputSwitcher';
import lazy { execAction } from '../../redux/ActionRegistry';
import lazy { FunctionActionType } from '../../redux/actions/FunctionActionType';
import lazy { CameraActionType } from '../../redux/actions/CameraActionType';
import lazy { ContextActionType } from '../../redux/actions/ContextActionType';
import lazy { ZoomParam } from './ZoomParam';
const TAG: string = 'ZoomFunction';
export type ModeData = {
mode?: ModeType;
cameraPosition?: camera.CameraPosition;
};
export type CameraStartedData = {
type: CameraStartType,
zoomRatio?: number
};
export class ZoomFunction extends BaseFunction {
private static readonly ZOOM_RATIO_DEFAULT: number = 1;
private static readonly ZOOM_RATIO_LENGTH: number = 2;
private static readonly ZOOM_REMAIN_HOLDING_TIME: number = 5;
private static readonly ZOOM_CONSTANT_TEN: number = 10;
private static readonly ZOOM_RATIO_CLOSE_FLASH: number = getCloseFlashZoom();
private static readonly isNeedHideFlashIcon: boolean = isNeedHideFlashIcon();
private mZoomRatio: number = 1;
private catchZoomRatio: number = 0;
private readonly RENDER_LOCATIONS: RenderLocation[] = [RenderLocation.NONE];
private conflictParam: ConflictParam;
private unprepareZoomTimerId: number = Number.MAX_VALUE;
getFunctionId(): FunctionId {
return FunctionId.ZOOM;
}
getDefaultValue(): number {
return ZoomFunction.ZOOM_RATIO_DEFAULT;
}
getRenderLocations(): RenderLocation[] {
return this.RENDER_LOCATIONS;
}
getRenderType(renderLocation: RenderLocation): RenderType {
return RenderType.NONE;
}
getUiElements(renderLocation: RenderLocation): Map<unknown, UiElement> {
return new Map();
}
setValue(inputValue: {
zoomRatio?: number,
isPrepareZoom?: boolean,
isSmoothZoom?: boolean,
isUnprepareZoom?: boolean,
unprepareDelay?: number
}): void {
HiLog.d(TAG, `setValue inputValue: ${JSON.stringify(inputValue)}.`);
if (inputValue === undefined || inputValue?.zoomRatio === undefined && inputValue?.isPrepareZoom === undefined &&
inputValue?.isUnprepareZoom === undefined) {
return;
}
if (this.unprepareZoomTimerId !== Number.MAX_VALUE) {
clearTimeout(this.unprepareZoomTimerId);
this.unprepareZoomTimerId = Number.MAX_VALUE;
}
if (inputValue?.zoomRatio === undefined) {
if (inputValue?.isPrepareZoom) {
this.mCameraProxy.prepareOrUnPrepareZoom(true);
} else if (inputValue?.isUnprepareZoom) {
this.unprepareZoom(inputValue.unprepareDelay);
}
return;
}
let oldZoomRatio = this.mZoomRatio;
this.mZoomRatio = inputValue?.zoomRatio;
if (inputValue?.isPrepareZoom) {
this.mCameraProxy.prepareOrUnPrepareZoom(true);
}
const isForeground = getStates().get<boolean>('contextReducer', 'isForeground');
if (isForeground) {
if (inputValue?.isSmoothZoom) {
this.mCameraProxy.setSmoothZoom(inputValue?.zoomRatio);
} else {
this.mCameraProxy.setZoomRatio(inputValue?.zoomRatio);
}
}
if (inputValue?.isUnprepareZoom) {
this.unprepareZoom(inputValue.unprepareDelay);
}
this.mStoreManager.postMessage(ZoomAction.updateStateZoomRatio(inputValue?.zoomRatio));
this.conflictFlashManage(oldZoomRatio, this.mZoomRatio);
this.controlLivePhotoManage(oldZoomRatio, this.mZoomRatio);
}
private unprepareZoom(delay?: number): void {
if (delay) {
this.unprepareZoomTimerId = setTimeout(() => {
this.mCameraProxy.prepareOrUnPrepareZoom(false);
this.unprepareZoomTimerId = Number.MAX_VALUE;
}, delay);
} else {
this.mCameraProxy.prepareOrUnPrepareZoom(false);
}
}
getValue(): number {
return this.mZoomRatio;
}
load(renderLocations: RenderLocation[]): void {
super.load(renderLocations);
this.mEventBus.on(FunctionActionType.ACTION_SET_FUNCTION_CONFLICT_PARAM, this.onConflictParamChange.bind(this),
this.mBase.hashCode());
this.mEventBus.on(CameraActionType.STARTED, this.zoomOnCameraStarted.bind(this), this.mBase.hashCode());
this.mEventBus.on([CameraActionType.CHANGE_MODE, CameraActionType.CHANGE_OUTPUT_TYPE,
CameraActionType.SWITCH_CAMERA, CameraActionType.SWITCH_CAMERA_CHANGE_MODE],
this.resetZoomRatio.bind(this), this.mBase.hashCode());
this.mEventBus.on(ContextActionType.ABILITY_ON_BACKGROUND, this.initZoomControlLiveState.bind(this),
this.mBase.hashCode());
this.onLoadFunction();
HiLog.i(TAG, 'load X.');
}
private onLoadFunction(): void {
HiLog.i(TAG, 'onLoadFunction X.');
this.mZoomRatio = ZoomOperation.getDefaultZoomRatio(getStates().get<ModeType>('modeReducer', 'mode'),
getStates().get<camera.CameraPosition>('cameraReducer', 'cameraPosition'));
this.mStoreManager.postMessage(ZoomAction.updateStateZoomRatio(this.mZoomRatio));
}
unload(renderLocations: RenderLocation[]): void {
super.unload(renderLocations);
HiLog.i(TAG, 'unload E.');
this.mEventBus.clear(this.mBase.hashCode());
HiLog.i(TAG, 'unload X.');
}
private conflictFlashManage(oldZoomRatio: number, zoomRatio: number, type?: CameraStartType): void {
oldZoomRatio = Math.round(oldZoomRatio * ZoomFunction.ZOOM_CONSTANT_TEN) / ZoomFunction.ZOOM_CONSTANT_TEN;
zoomRatio = Math.round(zoomRatio * ZoomFunction.ZOOM_CONSTANT_TEN) / ZoomFunction.ZOOM_CONSTANT_TEN;
if (((oldZoomRatio > ZoomFunction.ZOOM_RATIO_CLOSE_FLASH && zoomRatio > ZoomFunction.ZOOM_RATIO_CLOSE_FLASH) ||
(oldZoomRatio <= ZoomFunction.ZOOM_RATIO_CLOSE_FLASH && zoomRatio <= ZoomFunction.ZOOM_RATIO_CLOSE_FLASH)) &&
(type !== CameraStartType.CHANGE_MODE && type !== CameraStartType.SWITCH_CAMERA_CHANGE_MODE)) {
return;
}
if (!ZoomFunction.isNeedHideFlashIcon) {
this.handleFlashModeWhenAlwaysShowAllFlashIcon(zoomRatio);
return;
}
this.conflictParam = ConflictParam.emptyParam();
if (zoomRatio > ZoomFunction.ZOOM_RATIO_CLOSE_FLASH) {
const off: unknown = FlashMode.OFF;
const open: unknown = FlashMode.ALWAYS_OPEN;
this.conflictParam =
new ConflictParam().setLimitedValueSet(new ValueSet().setValues([<string> off, <string> open]));
}
this.mStoreManager.postMessage(TreasureBoxAction.updateByZoomChanged());
HiLog.i(TAG,
`conflictFlashManage: ${this.conflictParam?.limitedValue?.getValues().toString()}, zoom: ${zoomRatio}.`);
this.mConflictManager.setConflictParam(FunctionId.FLASH, this.conflictParam);
}
private handleFlashModeWhenAlwaysShowAllFlashIcon(zoomRatio: number): void {
let flashStatus: number = FeatureManager.getInstance().getFunction(FunctionId.FLASH).getValue();
HiLog.i(TAG, `handleFlashMode, ${ZoomFunction.ZOOM_RATIO_CLOSE_FLASH}, flashStatus = ${flashStatus}`);
let isClose: boolean = false;
const isAutoCloseFlash: boolean = this.mPreferencesService
.getPropValue(PersistType.FOREVER, PropTag.IS_AUTO_CLOSE_FLASH, false) as boolean;
if (zoomRatio > ZoomFunction.ZOOM_RATIO_CLOSE_FLASH) {
if (flashStatus === FlashMode.OFF && !isAutoCloseFlash) {
HiLog.i(TAG, `flashStatus is OFF`);
return;
}
isClose = true;
} else {
if (!isAutoCloseFlash) {
return;
}
isClose = false;
}
HiLog.i(TAG, `handleFlashMode, isClose = ${isClose}`);
this.mPreferencesService.putPropValue(PersistType.FOREVER, PropTag.IS_AUTO_CLOSE_FLASH, isClose);
this.mStoreManager.postMessage(ZoomAction.handleAutoCloseFlash(isClose));
this.mStoreManager.postMessage(TreasureBoxAction.updateByZoomChanged());
}
private controlLivePhotoManage(oldZoomRatio: number, zoomRatio: number, isNeedUpdate: boolean = false): void {
oldZoomRatio = Math.round(oldZoomRatio * ZoomFunction.ZOOM_CONSTANT_TEN) / ZoomFunction.ZOOM_CONSTANT_TEN;
zoomRatio = Math.round(zoomRatio * ZoomFunction.ZOOM_CONSTANT_TEN) / ZoomFunction.ZOOM_CONSTANT_TEN;
let livePhotoControlParam: boolean = false;
const hasZoomControlLive: boolean = getStates().get<boolean>('livePhotoReducer', 'isZoomControlLive');
HiLog.i(TAG, `zoom conflict live param: ${livePhotoControlParam},${hasZoomControlLive}.`);
if (hasZoomControlLive === !livePhotoControlParam) {
this.mStoreManager.postMessage(execAction('LivePhotoAction', 'handleZoomDelayControlLive',
livePhotoControlParam));
}
}
private initZoomControlLiveState(): void {
const hasZoomControlLive: boolean = getStates().get<boolean>('livePhotoReducer', 'isZoomControlLive');
if (hasZoomControlLive) {
HiLog.i(TAG, 'initZoomControlLiveState');
this.mStoreManager.postMessage(execAction('LivePhotoAction', 'handleZoomDelayControlLive', false));
}
}
* 重启流后要同步当前焦距给View层
*/
private zoomOnCameraStarted(data: CameraStartedData): void {
HiLog.i(TAG, `zoomOnCameraStarted. Start Type ${data.type}, ${data.zoomRatio}`);
let mode = getStates().get<ModeType>('modeReducer', 'mode');
const ratio = getStates().get<number>('zoomReducer', 'zoomRatio');
if ((data.type === CameraStartType.RESTART || data.type === CameraStartType.RECORD)) {
return;
}
let zoomRatio: number = data.zoomRatio || ratio;
HiLog.d(TAG, `zoomOnCameraStarted, getDefaultZoomRatio: ${zoomRatio}, ratio: ${ratio}`);
AppStorage.delete('wantZoomRatio');
if (ZoomOperation.getInstance().getRemainZoomRatio()) {
zoomRatio = ratio;
HiLog.d(TAG, `zoomOnCameraStarted. getRemainZoomRatio:${zoomRatio}`);
} else {
let conflictParam = ConflictParam.emptyParam();
this.mConflictManager.setConflictParam(FunctionId.FLASH, conflictParam);
}
const cameraPosition: camera.CameraPosition =
getStates().get<camera.CameraPosition>('cameraReducer', 'cameraPosition');
let oldZoomRatio = this.mZoomRatio;
this.mZoomRatio = zoomRatio;
if (this.mZoomRatio !== oldZoomRatio) {
HiLog.e(TAG, 'Inconsistent zoomRatio before and after camera started');
this.mStoreManager.postMessage(ZoomAction.updateStateZoomRatio(this.mZoomRatio));
this.setValue({
zoomRatio: this.mZoomRatio,
isPrepareZoom: true,
isUnprepareZoom: true,
unprepareDelay: ZoomParam.UNPREPARE_DELAY_TIME
});
} else {
const zoomRatioRange = this.mCameraProxy.getZoomRatioRange();
this.conflictFlashManage(oldZoomRatio, this.mZoomRatio, data.type);
this.controlLivePhotoManage(oldZoomRatio, this.mZoomRatio, true);
if (!zoomRatioRange || zoomRatioRange.length < ZoomFunction.ZOOM_RATIO_LENGTH) {
HiLog.e(TAG, 'zoomRatioRange undefined.');
return;
}
}
setTimeout((): void => {
ZoomOperation.getInstance().setRemainZoomRatio(false);
}, ZoomFunction.ZOOM_REMAIN_HOLDING_TIME);
}
private updateZoomRatio(): void {
let newZoomRatio = this.catchZoomRatio || this.mZoomRatio;
const conflicts: string[] = this.getRequiredConflicts()?.limitedValue?.getValues();
if (conflicts) {
this.catchZoomRatio = this.mZoomRatio;
newZoomRatio = Number(conflicts[0]);
} else {
this.catchZoomRatio = 0;
}
HiLog.i(TAG, `newZoomRatio: ${newZoomRatio}, mZoomRatio: ${this.mZoomRatio}, conflicts: ${conflicts?.toString()}.`);
this.mZoomRatio = newZoomRatio;
}
private onConflictParamChange(data: { id: number }): void {
const currentFunctionId = this.getFunctionId();
HiLog.i(TAG, `onConflictParamChange functionId: ${data.id}, currentFunctionId:${currentFunctionId}`);
if (currentFunctionId !== data.id) {
return;
}
this.updateZoomRatio();
this.mCameraProxy.setZoomRatio(this.mZoomRatio);
this.mStoreManager.postMessage(ZoomAction.updateStateZoomRatio(this.mZoomRatio));
HiLog.i(TAG, `onConflictParamChange ${this.mZoomRatio}.`);
}
private resetZoomRatio(data: ModeData): void {
HiLog.i(TAG, 'handleForeground');
if (ZoomOperation.getInstance().getRemainZoomRatio() && !PickerUtils.getIsPicker()) {
return;
}
let mode = data.mode || getStates().get<ModeType>('modeReducer', 'mode');
let cameraPosition =
data.cameraPosition || getStates().get<camera.CameraPosition>('cameraReducer', 'cameraPosition');
let zoomRatio: number = ZoomOperation.getDefaultZoomRatio(mode, cameraPosition);
this.mZoomRatio = zoomRatio;
this.mStoreManager.postMessage(ZoomAction.updateStateZoomRatio(zoomRatio));
this.conflictParam = ConflictParam.emptyParam();
this.mConflictManager.setConflictParam(FunctionId.FLASH, this.conflictParam);
this.initZoomControlLiveState();
}
}