transistorsoft / cordova-background-geolocation-lt

The most sophisticated background location-tracking & geofencing module with battery-conscious motion-detection intelligence for iOS and Android.
http://www.transistorsoft.com/shop/products/cordova-background-geolocation
Other
655 stars 277 forks source link

Task only starts in "even" executions.. #903

Closed dann95 closed 5 years ago

dann95 commented 5 years ago

Your Environment

{
        logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
        desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
        desiredOdometerAccuracy: 10,
        stationaryRadius: 25,
        url: serverRoute(ROUTES.v1.tracking),// a valid string for http endpoint...
        batchSync: false,
        autoSync: true,
        heartbeatInterval: 30,
        stopOnTerminate: false,
        startOnBoot: true,
        deferTime: 0
        preventSuspend: true
}

Expected Behavior

when call BackgroundGeolocation.start() it should start the task..

Actual Behavior

on first execution (and even executions ) the result of .start() is:


{
  "activityRecognitionInterval": 10000,
  "allowIdenticalLocations": false,
  "autoSync": true,
  "autoSyncThreshold": 0,
  "batchSync": false,
  "debug": false,
  "deferTime": 0,
  "desiredAccuracy": -1,
  "desiredOdometerAccuracy": 10,
  "disableElasticity": false,
  "disableStopDetection": false,
  "distanceFilter": 10,
  "elasticityMultiplier": 1,
  "enableHeadless": false,
  "enableTimestampMeta": false,
  "extras": {

  },
  "fastestLocationUpdateInterval": 10000,
  "forceReloadOnBoot": false,
  "forceReloadOnGeofence": false,
  "forceReloadOnHeartbeat": false,
  "forceReloadOnLocationChange": false,
  "forceReloadOnMotionChange": false,
  "forceReloadOnSchedule": false,
  "foregroundService": true,
  "geofenceInitialTriggerEntry": true,
  "geofenceProximityRadius": 1000,
  "geofenceTemplate": "",
  "headers": {

  },
  "headlessJobService": "com.transistorsoft.cordova.bggeo.BackgroundGeolocationHeadlessTask",
  "heartbeatInterval": 30,
  "httpRootProperty": "location",
  "httpTimeout": 60000,
  "isMoving": false,
  "locationTemplate": "",
  "locationTimeout": 60,
  "locationUpdateInterval": 1000,
  "locationsOrderDirection": "ASC",
  "logLevel": 5,
  "logMaxDays": 3,
  "maxBatchSize": -1,
  "maxDaysToPersist": 1,
  "maxRecordsToPersist": -1,
  "method": "POST",
  "minimumActivityRecognitionConfidence": 75,
  "notificationColor": "",
  "notificationLargeIcon": "",
  "notificationPriority": 0,
  "notificationSmallIcon": "",
  "notificationText": "Location Service activated",
  "notificationTitle": "",
  "params": {
    "playerID": "3411",
    "personalID": "6411",
    "partID": "0611"
  },
  "persist": true,
  "schedule": [

  ],
  "startOnBoot": true,
  "stationaryRadius": 25,
  "stopAfterElapsedMinutes": 0,
  "stopOnStationary": false,
  "stopOnTerminate": false,
  "stopTimeout": 5,
  "triggerActivities": "in_vehicle, on_bicycle, on_foot, running, walking",
  "url": "HIDDEN-FOR-SECURITY-REASONS",
  "enabled": true,
  "schedulerEnabled": false,
  "trackingMode": 1,
  "odometer": 0,
  "isFirstBoot": false
}

on second execution (and odd) the result of .start() is:

{
  "activityRecognitionInterval": 10000,
  "allowIdenticalLocations": false,
  "autoSync": true,
  "autoSyncThreshold": 0,
  "batchSync": false,
  "debug": false,
  "deferTime": 0,
  "desiredAccuracy": -1,
  "desiredOdometerAccuracy": 10,
  "disableElasticity": false,
  "disableStopDetection": false,
  "distanceFilter": 10,
  "elasticityMultiplier": 1,
  "enableHeadless": false,
  "enableTimestampMeta": false,
  "extras": {

  },
  "fastestLocationUpdateInterval": 10000,
  "forceReloadOnBoot": false,
  "forceReloadOnGeofence": false,
  "forceReloadOnHeartbeat": false,
  "forceReloadOnLocationChange": false,
  "forceReloadOnMotionChange": false,
  "forceReloadOnSchedule": false,
  "foregroundService": false,
  "geofenceInitialTriggerEntry": true,
  "geofenceProximityRadius": 1000,
  "geofenceTemplate": "",
  "headers": {

  },
  "headlessJobService": "com.transistorsoft.cordova.bggeo.BackgroundGeolocationHeadlessTask",
  "heartbeatInterval": 30,
  "httpRootProperty": "location",
  "httpTimeout": 60000,
  "isMoving": false,
  "locationTemplate": "",
  "locationTimeout": 60,
  "locationUpdateInterval": 1000,
  "locationsOrderDirection": "ASC",
  "logLevel": 5,
  "logMaxDays": 3,
  "maxBatchSize": -1,
  "maxDaysToPersist": 1,
  "maxRecordsToPersist": -1,
  "method": "POST",
  "minimumActivityRecognitionConfidence": 75,
  "notificationColor": "",
  "notificationLargeIcon": "",
  "notificationPriority": 0,
  "notificationSmallIcon": "",
  "notificationText": "Location Service activated",
  "notificationTitle": "",
  "params": {
    "playerID": "3411",
    "personalID": "6411",
    "partID": "0611"
  },
  "persist": true,
  "schedule": [

  ],
  "startOnBoot": true,
  "stationaryRadius": 25,
  "stopAfterElapsedMinutes": 0,
  "stopOnStationary": false,
  "stopOnTerminate": false,
  "stopTimeout": 5,
  "triggerActivities": "in_vehicle, on_bicycle, on_foot, running, walking",
  "url": "HIDDEN-FOR-SECURITY-REASONS",
  "enabled": true,
  "schedulerEnabled": false,
  "trackingMode": 1,
  "odometer": 0,
  "isFirstBoot": false
}

Steps to Reproduce

  1. wait deviceready event of cordova
  2. call configure (without reset:true)
  3. call BackgroundGeolocation.start((state) => {}) and console.log the result

Context

Enable the geolocation tracking service, but sometimes when i close the app and open again it wont start.. also if i go to GoogleChrome inspect console and try to start it manually typing BackgroundGeolocation.start()... it don't start...

christocracy commented 5 years ago

Don’t use method #configure. It’s deprecated in favor of new #ready.

dann95 commented 5 years ago

thats really weird, cause, executions of number (odd) 1, 3, 5 , 7 , 9 , 11 , 13.. it starts the tasks... on even executions, it don't starts the task..

dann95 commented 5 years ago

testing on another devices, if you want to, i can send u the .apk

christocracy commented 5 years ago

Are you watching the native logs? You’re doing something wrong on your end.

dann95 commented 5 years ago

No, i'm not looking for native logs, i just see that calling .start() gives foregroundService true/false depending on "number" of execution..

christocracy commented 5 years ago

foregroundService is enforced true by the plugin on SDK 23+.

dann95 commented 5 years ago

I got some logs of adb catlog, they are dirty with some private info, can i send it by e-mail?

christocracy commented 5 years ago

See you using #ready method (not #configure)?

It’s pointless to observe changes in foregroundService.

dann95 commented 5 years ago

but if user didn't allow app to use geolocation, what would .ready() do?

christocracy commented 5 years ago

Doesn’t matter: you MUST call #ready each and every time your app boots.

dann95 commented 5 years ago

ok.

dann95 commented 5 years ago

its needed ready and start() ?

christocracy commented 5 years ago

No. The plugin persists its state.

ready will automatically call #start upon itself if it was terminated with stopOnTerminate: false and was already #start-Ed.

christocracy commented 5 years ago

You don’t typically want to #start in the callback to #ready at all. I do this in the example just so people get the plugin started immediately.

Typically you’d execute #start / #stop from some UI interaction or app event.

dann95 commented 5 years ago

Well, calling Ready on the bootstrap, and "start" when "session" is confirmed, still not opening the background task, only on odd executions, even not..

christocracy commented 5 years ago

Show me your JavaScript

christocracy commented 5 years ago

opening the background task

What does this mean?

dann95 commented 5 years ago
import {
    EIOSLocationMode,
    ErrorGeolocationCallback,
    IGeolocationError,
    IGeolocationHardwareAbstraction,
    SuccessGeolocationCallback
} from '../hardware-interfaces';
import { EHardwareResourceStatus } from "./vendor/hardware-status";
import { Window } from './definitions/diagnostic';
import { CordovaPermissionsService } from "./permissions/cordova-permissions.service";
import { getClock, isValid } from "@colmeia/core/tools/utility";
import { CordovaGeolocationConstants } from "./cordova-hardware.constants";
import { ILocationCoordinates } from "@colmeia/core/tools/geo-util";
import { Location } from './transistorsoft-location';
import { SignalListenerService } from "../../../../component-comm/system-signals/sign-emissor/signal-listener";
import { PlayerCachedInfo } from "@colmeia/core/business/player-cached";
import { TGlobalUID } from "@colmeia/core/core-constants/types";
import { Injectable } from '@angular/core';

declare var window: Window;
declare var BackgroundGeolocation: any;

@Injectable({
    providedIn: 'root'
})
export class CordovaGeolocation implements IGeolocationHardwareAbstraction {

    private _successCallbacksMap: Map<number, SuccessGeolocationCallback> = new Map<number, SuccessGeolocationCallback>();
    private _errorCallbacksMap: Map<number, ErrorGeolocationCallback> = new Map<number, ErrorGeolocationCallback>();
    private _baterySaveMode: boolean = false;
    private _lastTimeReceivedPos: number = getClock();
    private player: PlayerCachedInfo;

    constructor(
        private permissions: CordovaPermissionsService,
        private listener: SignalListenerService
    ) {
        document.addEventListener('deviceready', () => {

            BackgroundGeolocation.onHttp(response => {
                console.log('[http] - ', response.success, response.status, response.responseText);
            });

            BackgroundGeolocation.onHeartbeat((event) => {
                this.onHeartbeatReceived();
            });

            BackgroundGeolocation.onLocation(location => {
                this.onLocationReceived(location);
            }, error => {
                this.onLocationErrorReceived(error);
            });

            BackgroundGeolocation.onMotionChange(event => {
                this.onLocationReceived(event.location);
            });

            BackgroundGeolocation.onPowerSaveChange(enabled => {
                this._baterySaveMode = enabled;
            });

            BackgroundGeolocation.ready({
                ...CordovaGeolocationConstants.Defaults,
                ...CordovaGeolocationConstants.IOSOnly,
                ...CordovaGeolocationConstants.AndroidOnly,
            });

            this.determineTrackingState();

            this.setEmergencyMode(false);
        });

        this.listener.listenToActivateGeoSignal(() => {
            this.determineTrackingState();
        });

        this.listener.listenToDeactivateGeoSignal(() => {
            this.determineTrackingState();
        });
    }

    private isTrackingOn(): boolean {
        if(! this.player) { //session isn't available.......
            return false;
        }
        const isLoged: boolean = isValid(this.player.getOriginalAvatarID());
        const isTrackingOn: boolean = this.player.isTrackingOn();
        return isLoged && isTrackingOn;
    }

    private async determineTrackingState(): Promise<void> {
        const isOn = this.isTrackingOn();
        const isAllowed = await this.isAllowed();

        if (isOn && isAllowed) {
            BackgroundGeolocation.start((state) => {
                console.log(state);
            })
        }
    }

    setCachedPlayer(player: PlayerCachedInfo) {
        this.player = player;
        console.warn("SETANDO NOVO PLAYER LOGADO PARA ENVIAR OS SINAIS DE GPS...")
        const idPlayer: TGlobalUID = player.getPlayerID(),
              personalID: TGlobalUID = player.getParticipantIDPersonalGroup(),
              part: TGlobalUID = player.getPersonalGroupParticipant(player.getOriginalAvatarID()).primaryID;

        console.log(player);
        if (player) {
            this.appendConfigs({
                params: {
                    "playerID": idPlayer,
                    "personalID": personalID,
                    "partID": part
                }
            });
        }

        this.determineTrackingState();
    }

    private onHeartbeatReceived(): void {
        console.warn('HEARTBEAT')
        BackgroundGeolocation.getCurrentPosition({
                persist: true,
                desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH
            }, this.onLocationReceived,
            this.onLocationErrorReceived
        );
    }

    setEmergencyMode(active: boolean): void {
        this.appendConfigs(
            (active) ? CordovaGeolocationConstants.EmergencyModeOn : CordovaGeolocationConstants.EmergencyModeOff
        );
    }

    private appendConfigs(settings: object): void {
        BackgroundGeolocation.setConfig(settings);
    }

    private normalizeLocation(loc: Location): ILocationCoordinates {
        return {
            uuid: loc.uuid,
            speed: loc.coords.speed,
            powerSaveMode: this._baterySaveMode,
            longitude: loc.coords.longitude,
            latitude: loc.coords.latitude,
            isMoving: loc.is_moving,
            heading: loc.coords.heading,
            gpsTimestamp: getClock(),
            batery: loc.battery.level,
            altitudeAccuracy: null,
            altitude: loc.coords.altitude,
            activity: loc.activity.type,
            accuracy: loc.coords.accuracy
        }
    }

    private normalizeError(err: PositionError): IGeolocationError {
        return {
            gpsTimestamp: getClock(),
            message: err.message
        }
    }

    private onLocationReceived = (location: Location) => {
        this._lastTimeReceivedPos = getClock();
        this._successCallbacksMap.forEach(cb => {
            cb(this.normalizeLocation(location));
        });
    };

    private onLocationErrorReceived = (err) => {
        this._errorCallbacksMap.forEach(cb => {
            cb(this.normalizeError(err));
        });
    };

    isEnabled(): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            window.cordova
                .plugins
                .diagnostic
                .isLocationEnabled((status: boolean) => {
                    resolve(status);
                }, (error) => {
                    resolve(false);
                });
        });
    }

    getAuthorizationStatus(): Promise<EHardwareResourceStatus> {
        return new Promise<EHardwareResourceStatus>((resolve, reject) => {
            window.cordova
                .plugins
                .diagnostic
                .getLocationAuthorizationStatus((status: EHardwareResourceStatus) => {
                    resolve(status);
                }, (error) => {
                    // Atenção, isso é questionável, não queremos na prática gerar erro desnecessário
                    resolve(EHardwareResourceStatus.NOT_REQUESTED)
                })
        });
    }

    isAllowed(): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            window.cordova
                .plugins
                .diagnostic
                .isLocationAuthorized((status: boolean) => {
                    resolve(status);
                }, (error) => {
                    resolve(false);
                })
        });
    }

    requestAuthorization(): Promise<EHardwareResourceStatus> {
        return new Promise<EHardwareResourceStatus>((resolve, reject) => {
            window.cordova
                .plugins
                .diagnostic
                .requestLocationAuthorization((status: EHardwareResourceStatus) => {
                    resolve(status);
                }, (error) => {
                    throw new Error(error)
                }, EIOSLocationMode.ALWAYS)
        });
    }

    getPosition(successCallback: SuccessGeolocationCallback, errorCallback: ErrorGeolocationCallback, options: object): void {
        BackgroundGeolocation.getCurrentPosition({
            persist: false,
            desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
            samples: 5
        }, (pos: Location) => {
            successCallback(this.normalizeLocation(pos));
        }, (err: PositionError) => {
            errorCallback(this.normalizeError(err));
        });
    }

    watchPosition(successCallback: SuccessGeolocationCallback, errorCallback: ErrorGeolocationCallback, options?: PositionOptions): number {
        const _id = this._successCallbacksMap.size + 1;

        this._successCallbacksMap.set(_id, successCallback);
        this._errorCallbacksMap.set(_id, errorCallback);

        return _id;
    }

    clearWatchPosition(id: number): void {
        this._errorCallbacksMap.delete(id);
        this._successCallbacksMap.delete(id);
    }

    getLastKnownPosition(): ILocationCoordinates {
        return null;
    }
}
dann95 commented 5 years ago

opening the background task

What does this mean?

at phone top bar it shows an small icon of application when "background" task is open, when i say that it dosn't open, no icon on topbar and no HTTP post.

dann95 commented 5 years ago

that service is constructed on "app.component.ts", it is an abstraction of geolocation For cordova build, for Browser we use another implementation.. this service is required on constructor of another service called "HardwareAbstractService"

christocracy commented 5 years ago

don't do this:

BackgroundGeolocation.ready({
                ...CordovaGeolocationConstants.Defaults,
                ...CordovaGeolocationConstants.IOSOnly,
                ...CordovaGeolocationConstants.AndroidOnly,
            });
            // NO.  You must not execute #start until the callback to #ready
            this.determineTrackingState();
BackgroundGeolocation.ready(config).then((state) => {
  this.determineTrackingState();
});
dann95 commented 5 years ago

ok, will try it.

dann95 commented 5 years ago

but at this momment, there is no session, cause: this.player is = undefined, so it won't call Start at this moment, only when setCachedPlayer its called.

christocracy commented 5 years ago

and you should probably place these into the callback to #ready, as well. Never execute #start until the callback to #ready fires:

this.listener.listenToActivateGeoSignal(() => {
            this.determineTrackingState();
        });

        this.listener.listenToDeactivateGeoSignal(() => {
            this.determineTrackingState();
        });

The method is called #ready because you're not supposed to do anything that requires location (eg: #start, #getCurrentPosition until the plugin is "ready".

dann95 commented 5 years ago

Yeah man, but its impossible to call .start before .ready finished, cause determineTrackingState checks for "CachedPlayer" and cached player is set after an http request..

christocracy commented 5 years ago

The problem is 100% guaranteed in your usage of the plugin.

Email me the result of #emailLog.

dann95 commented 5 years ago

is there any breaking change between 2.14 and 3? im thinking in update it, not sure if will solve our problem =/

dann95 commented 5 years ago

The problem is 100% guaranteed in your usage of the plugin.

Email me the result of #emailLog.

how i do that?

dann95 commented 5 years ago

have you an android device? i can pass the the .apk and u will reproduce exactly what i do =/

christocracy commented 5 years ago

how i do that?

See docs emailLog

See CHANGELOG. No breaking changes other than key change.

im thinking in update it, not sure if will solve our problem =/

It won't. This is not caused by the plugin. It's caused by your usage of it. However, 3.0.0 is a significant improvement in the behaviour of the Android service, which is no longer active when plugin is in stationary state.

have you an android device? i can pass the the .apk and u will reproduce exactly what i do =/

Dude...what do you think?? I have 13 of them:

dann95 commented 5 years ago

i reproduced this issue on any device, have u skype? or google talk?

christocracy commented 5 years ago

My rate for talking is $300/hour.

dann95 commented 5 years ago

opened.log didntopen.log

two logs, names represent what happened with background task xD

christocracy commented 5 years ago

I wan't filtered logs.

$ adb logcat -s TSLocationManager
dann95 commented 5 years ago

ok, i will ran it with filter, but when it dont open it shows: 04-10 18:23:22.399 17445 17606 W TSLocationManager: [c.t.l.a.BackgroundGeolocation$28 onPermissionGranted] 04-10 18:23:22.399 17445 17606 W TSLocationManager: ⚠️ Already started. Ignored

christocracy commented 5 years ago

04-10 18:23:22.399 17445 17606 W TSLocationManager: ⚠️ Already started. Ignored

Normal.

dann95 commented 5 years ago

ok, i ran with filter: normal.log wrong.log

dann95 commented 5 years ago

first execution = normal, than i close app, go to "running services" and stoped the background task, than try execute app again, and the only log i got is "wrong.log"...

dann95 commented 5 years ago

but... if i close app (after wrong.log) and look at "settings > running services, no service running", open app, and it opens normal again..

christocracy commented 5 years ago

Install 3.0.0.

dann95 commented 5 years ago

ok i will test with 3.0

dann95 commented 5 years ago

yeah im using the key >= 3.0

dann95 commented 5 years ago

must be that one of the image of annoucement, not the key i have in dashboard, right?

christocracy commented 5 years ago

There's a new 3.0.0 key waiting for you in the Dashboard.

In the future, customers post issues to private repo.

christocracy commented 5 years ago

Other than that, see License Validation Failure

dann95 commented 5 years ago

yeah i used that one of dashboard and it prints "invalid license when app opens"

dann95 commented 5 years ago

begins with 39 and ends with 50, i also did the "license validation failure" --nosave...

dann95 commented 5 years ago

trying again..

christocracy commented 5 years ago

If your key is in the AndroidManifest.xml, it will work. If it doesn't, your package-name in AndroidManifest must not match the key it was generated for.

dann95 commented 5 years ago

yeah, on version 3.0 it works fine, every app reboot open task again, but, it automatically closes but still "re-open" and send http yeah, is this expected behavior yeah?