Start a new topic
Solved

Location Services with Google API/SetLocation not working (Android)

Hello. Thank you so much in advance for your help.


I'm attempting to put some points of interest at specific locations around my campus, and I'm floundering around a bit trying to figure out how to get the Google API to work and to get setLocation to fire properly.  This is my first foray into app development (it's my capstone), as well as using location services and Wikitude, not to mention Android, in general, so I apologize if my code seems to be integrating several different methods of doing this. I have a hunch this is probably part of my problem. I am developing an Android app in Android Studio using the the Wikitude Javascript API.


THE PROBLEM

When I open my app to test, the Wikitude AR view camera displays fine, though there is a notification at the bottom that tells me it's trying to find my location.


WHAT I'VE DONE

To implement the Google API, I've been following tutorials and documentation online. I've gotten the key and gotten the certificate and registered my app with it and did all that, I've changed the correct files, and followed tutorials I believe to the letter. Location services are enabled for my app; I checked in the App Manager on my phone and saw that fine and course locations were enabled, as well as in the Manifest in my project. 


When I run my app, there is an error in the logcat that says


beginFailureResolution for ConnectionResult{statusCode=SIGN_IN_FAILED, resolution=null, message=null}


even though I'm signed into Google on the phone. Maybe I don't fully understand what exactly I'm needing to be signed into; my initial assumption was to my google account, which I'm assuming extends to being signed into Google Play services. Is that right?


I had put in alerts in the POI Javascript file in the onLocationChanged function (which, from what I understand, is fired when I call myArchitectView.setLocation(long, lat, 5.0), and there was nothing, even when I sent hard-coded values to setLocation.


I tried calling setLocation in the onCreate method just to see, both before and after the architectView.onCreate(), but it made my app crash both times.


I've edited nothing in the original Javascript, and it is correctly being referenced in the index.html, which is from the first POI example project.


My plan all along has been to use the Google API to fetch my current latitude and longitude, and then use those to send to setLocation(). Is this correct?


This is what I thought of off the top of my head when it comes to what I've done to figure it out. 


MY CODE


VIEWARACTIVITY.JAVA

package edu.snc.sncaugmentedtour;

import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.Manifest;
import android.location.Location;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.drive.Drive;
import com.google.android.gms.location.LocationServices;
import com.wikitude.architect.ArchitectStartupConfiguration;
import com.wikitude.architect.ArchitectView;

public class ViewARActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

private ArchitectView architectView;
private GoogleApiClient client;
private Location lastLocation;
private double latitude, longitude;
final static int FINE_LOCATION_PERMISSION_REQUEST = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_ar);

//set up our AR view
this.architectView = (ArchitectView)this.findViewById( R.id.architectView );
final ArchitectStartupConfiguration config = new ArchitectStartupConfiguration();
config.setLicenseKey("CRAZY LICENSE KEY HERE");

//ask for camera permission
//stackoverflow.com/questions/35451833/requesting-camera-permission-with-android-sdk-23*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
}
}

//create instance of Google APIClient to access location
//calls onConnected method
client = new GoogleApiClient.Builder(this)
.enableAutoManage(this /*fragment activity*/, this /*onConnectionFailedListener*/)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.setAccountName("hanna.raczek@snc.edu")
.build();

if(client == null){
client = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();

}

this.architectView.onCreate(config);
}

@Override
protected void onPostCreate(Bundle savedInstanceState){
//connect to location services
try{
client.connect();
}
catch (Exception e){

}
super.onPostCreate(savedInstanceState);
architectView.onPostCreate();

try {
this.architectView.load("file:///android_asset/poi_at_location/index.html");
}
catch (Exception e){

}

}

@Override
protected void onResume()
{
super.onResume();
architectView.onResume();
}

@Override
protected void onDestroy(){
try{
client.disconnect();
}
catch (Exception e){

}
super.onDestroy();
architectView.onDestroy();
}

@Override
protected void onPause(){
super.onPause();
architectView.onPause();
}

//implement ConnectionCallbacks onConnected method
@Override
public void onConnected(Bundle connectionHint){
//get permission for location services
//stackoverflow.com/questions/32491960/android-check-permission-for-locationmanager
//android documentation
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, FINE_LOCATION_PERMISSION_REQUEST);
}

//get current (last) location
lastLocation = LocationServices.FusedLocationApi.getLastLocation(client);
if(lastLocation != null){
latitude = lastLocation.getLatitude();
longitude = lastLocation.getLongitude();
architectView.setLocation(latitude, longitude, 5.0);
}

}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults){
switch(requestCode){
case FINE_LOCATION_PERMISSION_REQUEST:{

//if request is cancelled, result arrays are empty, so check for both an empty array and a permission denied
if(grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED){
if(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)){
String title = "Location services required";
String message = "Location services are required for the app's functionality. Please grant permission.";
createDialog(title, message);

//request permissions again
ActivityCompat.requestPermissions(ViewARActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, FINE_LOCATION_PERMISSION_REQUEST);
}
}
}
}
}

//handle a connection failure
@Override
public void onConnectionFailed(@NonNull ConnectionResult result){
String title = "Connection failed";
String message = "Connection to location services has failed.";
createDialog(title, message);
}

@Override
public void onConnectionSuspended(int reason){
String title = "Connection suspended";
String message = "Connection to location services has been interrupted.";
createDialog(title, message);
}

//stackoverflow.com/questions/26097513/android-simple-alert-dialog
protected void createDialog(String title, String message){
AlertDialog alertDialog = new AlertDialog.Builder(ViewARActivity.this).create();
alertDialog.setTitle(title);
alertDialog.setMessage(message);
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});

ANDROID MANIFEST

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.snc.sncaugmentedtour">

<!--
The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
Google Maps Android API v2, but you must specify either coarse or fine
location permissions for the 'MyLocation' functionality.
-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_GPS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-feature
android:name="android.hardware.camera2"
android:required="true" />
<uses-feature
android:name="android.hardware.location"
android:required="true" />
<uses-feature
android:name="android.hardware.sensor.accelerometer"
android:required="true" />
<uses-feature
android:name="android.hardware.sensor.compass"
android:required="true" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--
The API key for Google Maps-based APIs is defined as a string resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to sign the APK.
You need a different API key for each encryption key, including the release key that is used to
sign the APK for publishing.
You can define the keys for the debug and release targets in src/debug/ and src/release/.
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />

<activity
android:name=".ViewMapActivity"
android:label="@string/title_activity_view_map" />
<activity
android:name="ViewARActivity"
android:configChanges="screenSize|orientation"/>
</application>

</manifest>

POIATLOCATION.JS

// implementation of AR-Experience (aka "World")
var World = {
// true once data was fetched
initiallyLoadedData: false,

// POI-Marker asset
markerDrawable_idle: null,

// called to inject new POI data
loadPoisFromJsonData: function loadPoisFromJsonDataFn(poiData) {

/*
The example Image Recognition already explained how images are loaded and displayed in the augmented reality view. This sample loads an AR.ImageResource when the World variable was defined. It will be reused for each marker that we will create afterwards.
*/
World.markerDrawable_idle = new AR.ImageResource("assets/marker_idle.png");

/*
For creating the marker a new object AR.GeoObject will be created at the specified geolocation. An AR.GeoObject connects one or more AR.GeoLocations with multiple AR.Drawables. The AR.Drawables can be defined for multiple targets. A target can be the camera, the radar or a direction indicator. Both the radar and direction indicators will be covered in more detail in later examples.
*/
var markerLocation = new AR.GeoLocation(poiData.latitude, poiData.longitude, poiData.altitude);
var markerImageDrawable_idle = new AR.ImageDrawable(World.markerDrawable_idle, 2.5, {
zOrder: 0,
opacity: 1.0
});

// create GeoObject
var markerObject = new AR.GeoObject(markerLocation, {
drawables: {
cam: [markerImageDrawable_idle]
}
});

// Updates status message as a user feedback that everything was loaded properly.
World.updateStatusMessage('1 place loaded');
},

// updates status message shon in small "i"-button aligned bottom center
updateStatusMessage: function updateStatusMessageFn(message, isWarning) {

var themeToUse = isWarning ? "e" : "c";
var iconToUse = isWarning ? "alert" : "info";

$("#status-message").html(message);
$("#popupInfoButton").buttonMarkup({
theme: themeToUse
});
$("#popupInfoButton").buttonMarkup({
icon: iconToUse
});
},

// location updates, fired every time you call architectView.setLocation() in native environment
locationChanged: function locationChangedFn(lat, lon, alt, acc) {

/*
The custom function World.onLocationChanged checks with the flag World.initiallyLoadedData if the function was already called. With the first call of World.onLocationChanged an object that contains geo information will be created which will be later used to create a marker using the World.loadPoisFromJsonData function.
*/
if (!World.initiallyLoadedData) {
// creates a poi object with a random location near the user's location
var poiData = {
"id": 1,
"longitude": (lon + (Math.random() / 5 - 0.1)),
"latitude": (lat + (Math.random() / 5 - 0.1)),
"altitude": 100.0
};

World.loadPoisFromJsonData(poiData);
World.initiallyLoadedData = true;
}
},
};

/*
Set a custom function where location changes are forwarded to. There is also a possibility to set AR.context.onLocationChanged to null. In this case the function will not be called anymore and no further location updates will be received.
*/
AR.context.onLocationChanged = World.locationChanged;

1 Comment

Hello Hanna,

Since you are implementing a location strategy using Google Location services I put here relevant information regarding this implementation.

This is a documentation provided by Google demonstrating how to make your app location aware. In addition, you can refer to the code on github here regarding GoogleSamples with android play location and in this specific one regarding location updates.


Thanks

Eva

 

Login or Signup to post a comment