Mastering Image and Video Selection in HarmonyOS Next
Background
In chat applications, sending photos and videos from the album or capturing them via the camera is a common function. On Android and iOS, most apps use API-defined UIs to implement album photo/video selection and camera capture, supporting:
-
Album selection:
- Single or multiple selection.
- Option to select original image quality.
- Filters for video file size and duration.
- Tap to enlarge and preview images.
-
Camera capture:
- Tap to take photos, long-press to record videos.
- Video recording with max/min duration limits.
- Preview after capture/recording.
To implement these features in HarmonyOS apps, the system provides corresponding APIs requiring several permissions:
- System album read permission
- Microphone permission
- Camera permission
Introduction to HarmonyOS Permission System
Compared to Android, HarmonyOS offers stricter permission control, governed by its application permission management strategy. It provides a universal way for apps to access system resources (e.g., contacts) and capabilities (e.g., camera, microphone) to protect data (including user personal data) and functions from misuse or malicious use. Permission protection objects fall into two categories:
- Data: Personal data (photos, contacts, calendar, location) and device data (device ID, camera, microphone).
- Functions: Device functions (accessing camera/microphone, making calls, networking) and app functions (floating windows, creating shortcuts).
HarmonyOS classifies permissions by authorization method into:
- system_grant (System authorization): For non-sensitive data/operations with controlled impacts. The system automatically grants these permissions during app installation.
- user_grant (User authorization): For sensitive data/operations with potential severe impacts. Requires both manifest declaration and dynamic runtime user authorization via a pop-up.
For example, microphone and camera permissions are user-grant types, with detailed usage rationales listed in the Application Permission List. Apps must display requested user-grant permissions on the AppGallery detail page.
Another key concept is APL (Ability Privilege Level):
APL Level | Description |
---|---|
normal | Default level for all apps. |
system_basic | Provides basic system services. |
system_core | Provides core OS capabilities (cannot be configured for normal apps). |
Permissions are categorized by APL with varying access scopes:
APL Level | Description | Access Scope |
---|---|---|
normal | Access to ordinary system resources (e.g., Wi-Fi config, camera capture) with low privacy risk. | Apps with APL ≥ normal. |
system_basic | Access to basic OS services (e.g., system settings, authentication) with higher risk. | 1. Apps with APL ≥ system_basic. 2. Some permissions are restrictively open to normal apps (described as Restricted Permissions). |
system_core | Access to core OS resources critical for system operation. | 1. System apps with APL = system_core. |
Design Rationale
-
Authorization methods:
- system_grant is similar to Android’s normal permissions (declared in the manifest), avoiding unnecessary user prompts for non-sensitive operations (e.g., network access).
- user_grant requires explicit user consent for sensitive operations (e.g., camera access), aligning with privacy best practices.
-
Permission levels:
- normal: For common app functions (e.g., camera, microphone) with clear usage scenarios.
- system_core: Exclusive to system apps to prevent critical system damage (e.g., like Android’s root permissions).
- system_basic: For system apps, with restricted openness to normal apps (e.g., album read permission is restricted because it allows silent data transmission, unlike camera/microphone which require active user interaction).
System Picker: Permission-Free Resource Access
Selecting images/videos requires system_basic restricted permissions, while camera/microphone access needs user authorization. To streamline workflows, HarmonyOS provides system Pickers—components allowing permission-free resource selection:
- The system Picker (file/photo/contact selector) is implemented by an independent system process.
- Apps obtain resources via cross-process scheduling, with temporary and restricted access rights granted by the user’s explicit actions.
Selecting from Album
HarmonyOS provides photoAccessHelper.PhotoViewPicker()
to access album content. Example usage:
import { BusinessError } from '@kit.BasicServicesKit';
async function example01() {
try {
let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = 5;
let photoPicker = new photoAccessHelper.PhotoViewPicker();
photoPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(photoSelectResult));
}).catch((err: BusinessError) => {
console.error(`PhotoViewPicker.select failed with err: ${err.code}, ${err.message}`);
});
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`PhotoViewPicker failed with err: ${err.code}, ${err.message}`);
}
}
Key Objects
PhotoSelectOptions
(selection parameters)
Name | Type | Required | Description |
---|---|---|---|
isEditSupported11+ | boolean | No | Enable photo editing (default: true). |
isOriginalSupported12+ | boolean | No | Show "Select Original" button (default: true). Feature API: Available in ability models from API version 12. |
subWindowName12+ | string | No | Subwindow name. Feature API: Available in ability models from API version 12. |
Inherits from BaseSelectOptions
with additional configs:
Name | Type | Required | Description |
---|---|---|---|
MIMEType10+ | PhotoViewMIMETypes | No | Allowed media types (default: images + videos). Feature API: Available in ability models from API version 11. |
maxSelectNumber10+ | number | No | Max selection count (max: 500, default: 50). Feature API: Available in ability models from API version 11. |
isPhotoTakingSupported11+ | boolean | No | Enable in-picker camera (default: true). Feature API: Available in ability models from API version 11. |
isSearchSupported11+ | boolean | No | Enable search (default: true). Feature API: Available in ability models from API version 11. |
recommendationOptions11+ | RecommendationOptions | No | Photo recommendation configs. Feature API: Available in ability models from API version 11. |
preselectedUris11+ | Array | No | Preselected media URIs. Feature API: Available in ability models from API version 11. |
isPreviewForSingleSelectionSupported12+ | boolean | No | Enable full-screen preview for single selection (default: true). Feature API: Available in ability models from API version 12. |
PhotoSelectResult
(selection result)
Name | Type | Readable | Writable | Description |
---|---|---|---|---|
photoUris | Array | Yes | Yes | URIs of selected media (usable via temporary authorization with photoAccessHelper.getAssets). |
isOriginalPhoto | boolean | Yes | Yes | Whether original quality was selected. |
Distinguishing Image and Video URIs
Use photoAccessHelper
to parse media info:
public async uriGetAssets(uri: string): Promise<photoAccessHelper.PhotoAsset | undefined> {
try {
const context = getContext(this) as common.UIAbilityContext;
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
const predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
predicates.equalTo('uri', uri);
const fetchOption: photoAccessHelper.FetchOptions = {
fetchColumns: [photoAccessHelper.PhotoKeys.WIDTH, photoAccessHelper.PhotoKeys.HEIGHT, photoAccessHelper.PhotoKeys.TITLE, photoAccessHelper.PhotoKeys.DURATION],
predicates: predicates
};
const fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> = await phAccessHelper.getAssets(fetchOption);
const asset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();
Logg.i(TAG, `asset displayName: ${asset.displayName}`);
Logg.i(TAG, `asset uri: ${asset.uri}`);
Logg.i(TAG, `asset photoType: ${asset.photoType}`);
return asset;
} catch (error) {
Logg.e(TAG, `uriGetAssets failed: ${JSON.stringify(error)}`);
}
return undefined;
}
The photoType
property of photoAccessHelper.PhotoAsset
indicates media type (image/video).
Obtaining Video Thumbnails
Use getThumbnail()
on photoAccessHelper.PhotoAsset
to get a PixelMap
, then convert it to an image:
import { BusinessError } from '@kit.BasicServicesKit';
async function Demo() {
const readBuffer = new ArrayBuffer(96); // height * width * 4
if (pixelMap) {
pixelMap.readPixelsToBuffer(readBuffer, (error: BusinessError, res: void) => {
if (error) {
console.error(`Failed to read pixel data: ${error.code}, ${error.message}`);
return;
}
console.info('Pixel data read successfully.');
});
}
}
Use image.createImagePacker()
to write the PixelMap
to a file:
let imagePath: string | undefined = undefined;
const pixelMap = await matedata.getThumbnail();
const cacheDir = getContext().cacheDir;
imagePath = `${cacheDir}/thumbnail${Date.now()}.jpg`;
const dstFile = fs.openSync(imagePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
const packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };
await image.createImagePacker().packToFile(pixelMap, dstFile.fd, packOpts);
fs.close(dstFile);
Capturing via Camera
Two methods to launch the system camera:
Using Want
invokeCamera(callback: (uri: string) => void) {
const context = getContext(this) as common.UIAbilityContext;
const want: Want = {
action: 'ohos.want.action.imageCapture',
parameters: {
"callBundleName": context.abilityInfo.bundleName,
}
};
const result: (error: BusinessError, data: common.AbilityResult) => void =
(error: BusinessError, data: common.AbilityResult) => {
if (error && error.code !== 0) {
console.log(`${TAG_CAMERA_ERROR} ${JSON.stringify(error.message)}`);
return;
}
const resultUri: string = data.want?.parameters?.resourceUri as string;
if (callback && resultUri) {
callback(resultUri);
}
};
context.startAbilityForResult(want, result);
}
This method (referencing How to Call System Camera) doesn’t support video duration settings.
Using cameraPicker
import { cameraPicker as picker } from '@kit.CameraKit';
import { camera } from '@kit.CameraKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
const mContext = getContext(this) as common.Context;
async function demo() {
try {
const pickerProfile: picker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
const pickerResult: picker.PickerResult = await picker.pick(mContext,
[picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO], pickerProfile);
console.log(`Picker result: ${JSON.stringify(pickerResult)}`);
} catch (error) {
const err = error as BusinessError;
console.error(`Picker failed: ${err.code}`);
}
}
PickerProfile
(camera settings)
Name | Type | Required | Description |
---|---|---|---|
cameraPosition | camera.CameraPosition | Yes | Front/back camera. |
saveUri | string | No | URI to save captured media. |
videoDuration | number | No | Max video recording duration. |
PickerResult
(capture result)
Name | Type | Required | Description |
---|---|---|---|
resultCode | number | Yes | 0 for success, -1 for failure. |
resultUri | string | Yes | Captured media URI (public path if saveUri is empty; same as saveUri if writable). |
mediaType | PickerMediaType | Yes | Media type (photo/video). |
Summary
This article introduces image/video selection and capture capabilities in HarmonyOS Next, focusing on permission-free implementations via photoAccessHelper
and cameraPicker
.
Top comments (0)