Extending Activity class in NativeScript+Angular


#1

Hello,
I need to use a live camera preview in my application (just preview without taking pictures). As there is no appropriate plugin for this, I started to implement the native approach, using strategy described on Android developers site (Camera API. I’m using NativeScript and Angular.
So, I created a service, which is to be responsible for handling camera actions:
camera.service.ts:

import { Injectable } from '@angular/core';
import * as application from "tns-core-modules/application";
import { CameraPreview } from '../cameraPreview/cameraPreview';

@Injectable()
export class CameraService {
    private mCamera = android.hardware.Camera;
    constructor() {
  
    }
    
    checkCameraHardware(): boolean {
        const PackageManager = android.content.pm.PackageManager;
        const context = application.android.context;
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
            // this device has a camera
            return true;
        } else {
            // no camera on this device
            return false;
        }
    }

    getCameraInstance(){
        console.log("Getting camera instance");
        let cInstance = null;
        try {
            cInstance = this.mCamera.open(); // attempt to get a Camera instance
        }
        catch (e){
            console.log("Camera is not available (in use or does not exist)" + e.message);
        }
        return cInstance; // returns null if camera is unavailable
    }
    
    getCameraPreview(context, camera) {
        console.log("Getting CameraPreview object");
        let cameraPreview = new CameraPreview(context, camera);
        return cameraPreview;
    }
    
    releaseCamera(){
        //this.mCamera.release();
    }
}

CameraPreview class in cameraPreview.ts:

const surfaceView = android.view.SurfaceView;

export class CameraPreview extends surfaceView implements android.view.SurfaceHolder.Callback {
    private mHolder;
    private mCamera;

    constructor(context, camera) {
        console.log("Entering CameraPreview constructor");
        super(context);
        this.mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        console.log("Before getHolder");
        this.mHolder = this.getHolder();
        this.mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        //this.mHolder.setType(android.view.SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

   surfaceCreated(holder) {
        console.log("Entering CameraPreview surfaceCreated");
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            this.mCamera.setPreviewDisplay(holder);
            this.mCamera.startPreview();
        } catch (e) {
            console.log("Error setting camera preview: " + e.message);
        }
    }

    surfaceDestroyed(holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    surfaceChanged(holder, format, width, height) {
        console.log("Entering CameraPreview surfaceChanged");
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (this.mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            this.mCamera.stopPreview();
        } catch (e){
          console.log("Tried to stop a non-existent preview");
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            this.mCamera.setPreviewDisplay(this.mHolder);
            this.mCamera.startPreview();

        } catch (e){
            console.log("Error starting camera preview: " + e.message);
        }
    }
}

Bootstrapping everything in the app.module.ts:

import { NgModule, NgModuleFactoryLoader, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";

import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";

import { CameraService } from "./services/camera.service";



@NgModule({
    bootstrap: [
        AppComponent
    ],
    imports: [
        NativeScriptModule,
        AppRoutingModule
    ],
    declarations: [
        AppComponent
    ],
    schemas: [
        NO_ERRORS_SCHEMA
    ],
    providers : [
        CameraService
        
    ]
})
export class AppModule { }

In order to use preview image, obtained from the camera I tried to make a custom Activity class by extending native android.app.Activity, using instructions from Extending activity:
activity.android.ts:

import {setActivityCallbacks, AndroidActivityCallbacks} from "ui/frame";
import { CameraService } from "./services/camera.service";

@JavaProxy("org.myApp.MainActivity")
class Activity extends android.app.Activity {
    private _callbacks: AndroidActivityCallbacks;

    constructor(private cameraService: CameraService){
        super();
    }

    public onCreate(savedInstanceState: android.os.Bundle): void {
        if (!this._callbacks) {
            setActivityCallbacks(this);
        }

        this._callbacks.onCreate(this, savedInstanceState, super.onCreate);

        console.log("onCreate called");
       
        // Create our Preview view and set it as the content of our activity.
        const LinearLayout = android.widget.LinearLayout;
        const TextView = android.widget.TextView;
        const LayoutParams = android.widget.LinearLayout.LayoutParams;
        
        // creating LinearLayout
        let linLayout = new LinearLayout(this);
        // specifying vertical orientation
        linLayout.setOrientation(LinearLayout.VERTICAL);
        // creating LayoutParams  
        let linLayoutParam = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 
        // set LinearLayout as a root element of the screen 
        
        this.setContentView(linLayout, linLayoutParam);
                  
        let mPreview = this.cameraService.getCameraPreview(this, this.cameraService.getCameraInstance());

        linLayout.addView(mPreview);
    }

    public onSaveInstanceState(outState: android.os.Bundle): void {
        this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);
    }

    public onStart(): void {
        this._callbacks.onStart(this, super.onStart);
    }

    public onStop(): void {
        this._callbacks.onStop(this, super.onStop);
    }

    public onDestroy(): void {
        this._callbacks.onDestroy(this, super.onDestroy);
    }

    public onBackPressed(): void {
        this._callbacks.onBackPressed(this, super.onBackPressed);
    }

    public onRequestPermissionsResult(requestCode: number, permissions: Array<string>, grantResults: Array<number>): void {
        this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
    }

    public onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {
        this._callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult);
    }
}

My AndroidManifext.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="__PACKAGE__"
	android:versionCode="10000"
	android:versionName="1.0">

	<supports-screens
		android:smallScreens="true"
		android:normalScreens="true"
		android:largeScreens="true"
		android:xlargeScreens="true"/>

	<uses-sdk
		android:minSdkVersion="17"
		android:targetSdkVersion="__APILEVEL__"/>

	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
	<uses-permission android:name="android.permission.INTERNET"/>
	<uses-permission android:name="android.permission.CAMERA" />
	<uses-feature android:name="android.hardware.camera" />

	<application
		android:name="com.tns.NativeScriptApplication"
		android:allowBackup="true"
		android:icon="@drawable/icon"
		android:label="@string/app_name"
		android:theme="@style/AppTheme">

		<activity
			android:name="org.myApp.MainActivity"
			android:label="@string/title_activity_kimera"
			android:configChanges="keyboardHidden|orientation|screenSize"
			android:theme="@style/LaunchScreenTheme">

			<meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" />

			<intent-filter>
				<action android:name="android.intent.action.MAIN" />
				<category android:name="android.intent.category.LAUNCHER" />
			</intent-filter>
		</activity>
		<activity android:name="com.tns.ErrorReportActivity"/>
	</application>
</manifest>

So, after I compile and build my app I’m getting an empty white screen in the application. Also, console.logs, which I have in my CamerService are not being output in the console. It looks like the service was not injected correctly, though there are no errors in the console.
Can anyone give a hint of the possible reasons?


#2

If Preview is just want you need, why don’t you use nativescript-camera-plus plugin and add preview to your UI.


#3

Thanks for your reply!
Actually, I was not exactly accurate in my wording. Preview is not all that is required. I will also need to postprocess the image and also to use some camera features, like viewing angles. So, I hope, that native android.hardware.camera will do the job.
Could you please comment what is wrong in my initial approach with extending Activity class?


#4

Because you are replacing the Activity content itself, instead you can add the camera preview directly into a palceholder.


#5

Thanks for the hint!
I have tryed the placeholder and it should work. But the problem now is in my CameraPreview class. The system can’t resolve addCallback method. It looks, like getHolder doesn’t give a SurfaceHolder object, but rather a SurfaceView object.