This is an old revision of the document!
Datele cu privire la localizare îmbunătățesc experiența utilizatorului, întrucât unele informații furnizate de aplicații pot fi contextualizate în funcție de regiunea în care acesta se găsește în mod curent. O astfel de oportunitate poate fi exploatată cu atât mai mult în cadrul dispozitivelor mobile, care dispun de componente specializate pentru determinarea automată a poziției geografice curente (senzor pentru GPS, folosirea informațiilor furnizate de celula de telefonie mobilă).
În cadrul SDK-ului Android, sunt implementate API-uri pentru proiectarea și dezvoltarea unor aplicații care pun la dispoziția utilizatorilor informații cu privire la locația în care se află, disponibile prin intermediul unor metode, fără a fi necesară interacțiunea propriu-zisă cu componentele responsabile cu determinarea acestor date. Totodată, există posibilitatea de a identifica punctele de interes care se găsesc în proximitatea utilizatorului, la un moment dat de timp.
Astfel, funcționalitățile oferite pentru dezvoltatori sunt:
android.location
).
1. În cadrul Consolei Google API, se activează API-ul Google Maps Android API, generându-se totodată și o cheie Android prin care aplicația care rulează pe dispozitivul mobil va putea să acceseze o astfel de funcționalitate.
Se va afișa o fereastră din care poate fi selectat proiectul dorit (împărțite în categoriile Recent, respectiv All). În situația în care nu există nici un proiect, acesta poate fi creat, prin accesarea pictogramei corespunzătoare semnului +.
Pentru fiecare proiect trebuie să se precizeze următorii parametri:
În situația în care există un singur proiect, acesta va fi selectat în mod implicit ca proiect curent.
Acest API nu va putea fi însă utilizat în situația în care nu sunt create credențialele (o cheie pentru API), necesare pentru a putea accesa orice serviciu Google.
keytool
, se generează semnătura digitală a mașinii de pe care se va dezvolta aplicația Android (pentru a putea utiliza acest utilitar, calea căte Java trebuie să se găsească în variabila de mediu $PATH
, respectiv %PATH
).student@eim2017:~$ export PATH=$PATH:/usr/local/java/jdk1.8.0_131/bin student@eim2017:~$ keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
Windows
C:\Users\Student> set PATH=%PATH%;C:\Program Files\Java\jdk_1.8.0_131\bin C:\Users\Student> keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
Vor fi furnizate mai multe tipuri de amprente digitale, pentru cheia publică de tip Android fiind necesară cea de tip SHA-1
Alias name: androiddebugkey Creation date: Mar 5, 2015 Entry type: PrivateKeyEntry Certificate chain length: 1 Certificate[1]: Owner: CN=Android Debug, O=Android, C=US Issuer: CN=Android Debug, O=Android, C=US Serial number: 4a38a96a Valid from: Thu Mar 05 13:17:44 EET 2015 until: Sat Feb 25 13:17:44 EET 2045 Certificate fingerprints: MD5: FC:1F:95:45:78:ED:50:C6:EE:8E:02:0A:3D:A5:80:D3 SHA1: C7:02:98:BB:AD:1C:6E:D1:3A:35:50:8B:88:78:B6:D3:B7:9F:66:C0 SHA256: B3:D9:98:33:92:71:2D:CE:65:19:89:73:2A:64:3C:97:B9:37:A1:93:8C: 50:4F:E1:13:C4:21:C7:08:94:AC:A5 Signature algorithm name: SHA256withRSA Version: 3 Extensions: #1: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 99 78 63 24 A0 64 DF A8 67 45 8E 82 C6 8E 53 D1 .xc$.d..gE....S. 0010: B8 C1 89 75 ...u ] ]
se accesează butonul Add package name and fingerprint pentru a se specifica denumirea pachetului corespunzător aplicației Android care va accesa API-ul respectiv și certificatul SHA-1
În secțiunea Credentials pot fi vizualizate cheile pentru API generate anterior
2. Pe dispozitivul mobil (fizic sau virtual) pe care se va rula aplicația care accesează serviciul de localizare, trebuie să se găsească cea mai recentă versiune de Google Play Services, asociindu-se totodată contul de utilizator Google pentru care s-a generat cheia publică.
3. Se instalează SDK-ul Google Play Services, necesar accesării serviciului de localizare prin intermediul unei aplicații Android.
student@eim2017:~$ cd /opt/android-sdk-linux/tools student@eim2017:/opt/android-sdk-linux/tools$ sudo ./android
C:\Users\Student> cd "..\..\Program Files (x86)\Android\android-sdk\tools" C:\Program Files (x86)\Android\android-sdk\tools>android.bat
Astfel, se instalează următoarele pachete:
Posibilitatea de instalare a acestor pachete există și din mediul integrat de dezvoltare Android Studio, prin accesarea opțiunii Tools → Android → SDK Manager, secțiunea SDK Tools.
4. În mediul integrat de dezvoltare Android Studio, se creează un proiect corespunzător unei aplicații Android, având următoarele proprietăți:
build.gradle
să se specifice dependința către biblioteca Google Play Services (com.google.android.gms:play-services
), în secțiunea dependencies
: ... dependencies { ... compile 'com.google.android.gms:play-services:10.2.4' }
AndroidManifest.xml
<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.INTERNET" /> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
android.permission.ACCESS_COARSE_LOCATION
- obține locația utilizatorului folosind informațiile preluate prin rețele fără fir și datele corespunzătoare celulei în care se găsește dispozitivul mobil;android.permission.ACCESS_FINE_LOCATION
- procură locația utilizatorului prin intermediul coordonatelor obținute de la sistemul de poziționare (GPS - eng. Global Positioning System);android.permission.ACCESS_NETWORK_STATE
- verifică starea conectivității în rețea, astfel încât să se determine dacă este posibil ca datele să fie descărcate sau nu;android.permission.INTERNET
- determină starea conectivității la Internet;com.google.android.providers.gsf.permission.READ_GSERVICES
- preia informațiile puse la dispoziție prin intermediul Google Play Services;android.permission.WRITE_EXTERNAL_STORAGE
- utilizează un spațiu de stocare pentru informațiile legate de hărți;<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<application> … </application>
se indică:<metadata android:name="com.google.android.maps.v2.API_KEY" android:value="AIzaSyARiJhQ-Lj6HnzQwq7MqAvjWQtNkjVcprs" />
<metadata android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<permission android:name="ro.pub.cs.systems.eim.lab10.googlemaps.permission.MAPS_RECEIVE" android:protectionLevel="signature" /> <uses-permission android:name="ro.pub.cs.systems.eim.lab10.googlemaps.permission.MAPS_RECEIVE" />
proguard-rules.pro
din directorul app), astfel încât acesta să nu elimine clasele necesare:-keep class * extends java.util.ListResourceBundle { protected Object[][] getContents(); } -keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable { public static final *** NULL; } -keepnames @com.google.android.gms.common.annotation.KeepName class * -keepclassmembernames class * { @com.google.android.gms.common.annotation.KeepName *; } -keepnames class * implements android.os.Parcelable { public static final ** CREATOR; }
Pentru accesarea funcționalităților legate de locație pe dispozitivul fizic este necesar să se activeze opțiunea Location din secțiunea de configurări (Settings → Personal).
Valoarea configurației Location trebuie să aibă valoarea On, pentru ca serviciile de localizare să poată fi utilizate. De asemenea, sunt indicate aplicațiile Android care au folosit serviciile de localizare.
Se poate controla acuratețea informațiilor furnizate, raportat la consumul de energie, prin intermediul opțiunilor disponibile în secțiunea Location Mode din secțiunea de configurări (Settings → Personal → Location → Mode).
Pentru accesarea funcționalităților legate de locație pe dispozitivul virtual Genymotion este necesar să se activeze serviciul GPS, accesibil din meniul lateral sau folosind combinația de taste Ctrl + 2 (se selectează valoarea On).
Alte informații care pot fi configurate sunt:
De asemenea, este implementată și funcționalitatea prin intermediul căreia poate fi vizualizată poziția precizată în cadrul unei hărți Google.
Pentru accesarea funcționalităților legate de locație pe dispozitivul virtual AVD este necesar ca în secțiunea de configurări corespunzătoare (Settings → Location access) să se specifice următorii parametri:
Controlul poziției curente poate fi realizat prin intermediul perspectivei Android Debug Monitor din Android Studio, unde, în secțiunea Emulator Control → Location Control se stabilesc valorile pentru latitudine și longitudine (în panoul Manual, în format decimal sau sexagesimal), după care se apasă butonul Send. Informații cu privire la locațiile disponibile pot fi precizate și sub forma unor fișiere gpx sau kml, care pot fi încărcate.
Harta Google este implementată în SDK-ul Android:
MapView
șiMapFragment
sunt disponibile începând cu nivelul de API 12, asigurarea compatibilității cu versiunile anterioare fiind realizată prin intermediul bibliotecilor de suport.
Astfel, integrarea unei hărți Google se poate implementa prin specificarea resursei aferente în fișierul XML care descrie interfața grafică.
<fragment android:id="@+id/google_map" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.MapFragment" />
Pe baza controalelor grafice MapView
sau MapFragment
, se poate obține o instanță a unui obiect GoogleMap, prin intermediul căruia sunt invocate toate funcționalitățile pentru operații legate de localizarea pe hartă.
De regulă, inițializarea este realizată pe una dintre metodele onStart()
sau onResume()
, după ce în prealabil au fost încărcate toate controalele grafice pentru interacțiunea cu utilizatorul.
onMapReady()
a clasei ascultător nu va fi apelată în situația în care serviciul Google Play Services nu este disponibil pe dispozitivul mobil sau obiectul este distrus imediat după ce a fost creat.
GoogleMap
trebuie realizată pe firul de execuție al interfeței grafice (principal), în caz contrar generându-se o excepție.
GoogleMap googleMap = null; // ... if (googleMap == null) { ((MapFragment)getFragmentManager().findFragmentById(R.id.google_map)).getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(GoogleMap readyGoogleMap) { googleMap = readyGoogleMap; } }); }
Funcționalitățile pe care le pune la dispoziție un obiect de tipul GoogleMap
sunt:
1. gestiunea reperelor de pe harta Google, prin intermediul elementelor MarkerOptions, pentru care se pot preciza următoarele informații:
double
), prin metoda position(LatLng) marker.position(new LatLng( Double.parseDouble(latitudeContent), Double.parseDouble(longitudeContent) ) );
marker.title(nameContent);
marker.icon(BitmapDescriptorFactory.defaultMarker(Utilities.getDefaultMarker(markerTypeSpinner.getSelectedItemPosition())));
assets
, ce conține resurse externe;drawable
, ce conține resurse care pot fi desenate.marker.snippet(descriptionContent);
marker.visible(true);
Un astfel de obiect este atașat unei hărți Google prin intermediul metodei addMarker(MarkerOptions).
googleMap.addMarker(marker);
Alte elemente grafice care pot fi vizualizate sunt:
Toate aceste controale pot fi înlăturate în momentul în care este folosită metoda clear().
2. poziționarea la anumite coordonate GPS (latitudine, longitudine) este realizată prin actualizarea locației la care se găsește camera prin care este vizualizată harta Google, funcționalitate implementată de clasa CameraUpdate; un astfel de obiect este obținut de regulă prin metoda statică newCameraPosition(CameraPosition) din clasa fabrică CameraUpdateFactory
, prin care pot fi controlate și alte proprietăți (nivelul de detaliere, vizualizarea unui anumit areal geografic); metoda animateCamera(CameraUpdate) realizează transferul dintre coordonatele vechi și coordonatele noi prin intermediul unei animații.
CameraPosition cameraPosition = new CameraPosition.Builder().target(new LatLng(latitude, longitude)) .build(); googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
Metoda animateCamera()
este supraîncărcată, astfel încât să se poată preciza:
onFinish()
) sau a fost întreruptă (onCancel()
).În situația în care este în desfășurare o animație, aceasta poate fi oprită printr-un apel al metodei stopAnimation().
3. marcarea locației curente este realizată prin intermediul metodei setMyLocationEnabled(boolean); de asemenea, este disponibil un control prin intermediul căruia utilizatorul poate activa sau dezactiva această funcționalitate;
4. vizualizarea unor informații suplimentare:
Tipurile de hărți Google implementate sunt:
GoogleMap.MAP_TYPE_NORMAL
- harta politică;GoogleMap.MAP_TYPE_TERRAIN
- harta fizică (nu include și drumuri); GoogleMap.MAP_TYPE_SATELLITE
- vedere din satelit;GoogleMap.MAP_TYPE_HYBRID
- combinație hibridă.Specificarea unui tip de hartă se realizează prin intermediul metodei setMapType().
Gestiunea unei hărți Google sub formă de imagine este realizată prin intermediul metodei snapshot(GoogleMap.SnapshotReadyCallback, al cărui obiect ascultător furnizează resursa grafică (în format Bitmap
) în momentul în care este disponibilă (se apelează automat metoda onSnapshotReady(Bitmap)
.
Funcționalitatea pe care o oferă harta Google utilizatorului poate fi controlată și prin intermediul obiectului asociat de tip UiSettings, obținut prin apelul metodei getUiSettings():
Pentru interacțiunea cu utilizatorul au fost definite mai multe clase ascultător, ale căror metode semnalează declanșarea unor evenimente specifice:
API-ul Android pune la dispoziția utilizatorilor un furnizor integrat de servicii de localizare, prin care aceștia pot specifica anumiți parametrii de configurare, cum ar fi nivelul de precizie și gradul de utilizare al bateriei.
Funcționalitatea legată de gestiunea locației curente (ca de altfel toate funcționalitățile legate de biblioteca Google Play Services) este disponibilă prin intermediul unui obiect de tip GoogleApiClient, a cărui instanță este obținută de regulă pe metoda onCreate()
a aplicației Android, eliberarea resurselor corespunzătoare acesteia fiind făcută pe metoda onDestroy()
.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... googleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); } @Override protected void onDestroy() { // ... googleApiClient = null; super.onDestroy(); }
Pentru clientul Google API, s-a indicat funcționalitatea pentru care va fi accesat (LocationServices.API
) prin intermediul metodei addApi()
, precum și clasele ascultător pentru evenimentele care pot fi generate legat de acesta (metodele corespunzătoare fiind asincrone):
onConnected(Bundle)
- apelată în momentul în care clientul Google API s-a conectat cu succes la funcționalitatea dorită;onConnectionSuspended(int)
- apelată în momentul în care clientul Google API este deconectat (temporar) de la funcționalitatea solicitată, indicându-se și motivul care a generat un astfel de comportament:CAUSE_NETWORK_LOST
- deconectare de la Internet;CAUSE_SERVICE_DISCONNECTED
- oprirea serviciului corespunzător.onConnectionFailed(ConnectionResult)
, specificând rezultatul ce conține codul de eroare.
Operațiile de conectare / deconectare a clientului Google API la serviciu trebuie realizate în contextul metodelor care controlează ciclul de viață al aplicației Android, astfel:
connect()
se apelează în cadrul metodei onStart()
;disconnect()
se apelează în cadrul metodei onStop()
.@Override protected void onStart() { super.onStart(); googleApiClient.connect(); // ... } @Override protected void onStop() { // ... if (googleApiClient != null && googleApiClient.isConnected()) { googleApiClient.disconnect(); } super.onStop(); }
Momentul în care sunt disponibile informațiile cu privire la cea mai recentă locație a dispozitivului mobil este determinat de conectarea cu succes a clientului Google API, motiv pentru care se obișnuiește ca aceste date să fie interogate pe metoda onConnected(Bundle)
a intefeței GoogleApiClient.ConnectionCallbacks
, apelată în mod automat la producerea evenimentului respectiv. În acest sens, se apelează metoda getLastLocation(GoogleApiClient) din clasa FusedLocationProviderApi.
@Override public void onConnected(Bundle connectionHint) { Log.i(Constants.TAG, "onConnected() callback method has been invoked"); lastLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient); // ... }
Informațiile referitoare la locația curentă sunt descrise sub forma unui obiect Location, care conține mai multe informații, precum:
De asemenea, pentru acest tip de obiect pot fi asociate informații suplimentare, sub forma unui Bundle
, în câmpul extras
.
Totodată, pot fi obținute actualizări periodice cu privire la locația curentă, pe baza furnizorilor disponibili (transfer de date în rețeaua GSM / fără fir, sistemul global de poziționare GPS) funcționalitate utilă în momentul în care se dorește să se identifice activitatea pe care o desfășoară utilizatorul pentru a contextualiza conținutul oferit în funcție de aceasta.
Acuratețea datelor generate poate fi controlată și prin intermediul configurațiilor conținute în solicitarea corespunzătoare, exprimată prin intermediul unui obiect de tip LocationRequest:
setFastestInterval()
să fie corespunzătoare, astfel încât să nu fie furnizate valori care nu pot fi utilizate.
requestLocationUpdates()
și removeLocationUpdates()
;PRIORITY_BALANCED_POWER_ACCURACY
- gradul de acuratețe este relativ (aproximativ 100 de metri), consumul de energie fiind moderat;PRIORITY_HIGH_ACCURACY
- gradul de acuratețe este ridicat (aproximativ 10 metri), pe baza informațiilor provenite de la sistemul global de poziționare (GPS), cu un consum mare de energie;PRIORITY_LOW_POWER
- gradul de acuratețe este scăzut (aproximativ 10 kilometri), sursele folosite fiind rețeaua mobilă / fără fir, cu un consum mic de energie;PRIORITY_NO_POWER
- utilizat atunci când se dorește transmiterea de actualizări periodice cu privire la locația curentă, fără un impact semnificativ asupra consumului de energie (de regulă, astfel de informații sunt preluate de la alte aplicații);
De regulă, instanțierea unui obiect de tip LocationRequest
precum și precizarea parametrilor ce caracterizează solicitările cu privire la actualizările periodice sunt realizate o singură dată, în momentul în care aplicația Android este pornită (pe metoda onCreate()
), urmând ca eliberarea resurselor să fie realizată atunci când aplicația Android este oprită (pe metoda onDestroy()
).
locationRequest = new LocationRequest(); locationRequest.setInterval(Constants.LOCATION_REQUEST_INTERVAL); locationRequest.setFastestInterval(Constants.LOCATION_REQUEST_FASTEST_INTERVAL); locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
O aplicație Android primește actualizări periodice cu privire la locația curentă între momentele de timp la care specifică explicit acest lucru, prin apelul metodelor corespunzătoare:
Întrucât aceste metode primesc un parametru de tip GoogleApiClient
, este necesar ca acesta să fie nenul și să fie conectat. Din acest motiv, o practică curentă vizează realizarea suplimentară a acestor verificări anterior invocării lor propriu-zise.
protected void startLocationUpdates() { LocationServices.FusedLocationApi.requestLocationUpdates( googleApiClient, locationRequest, this ); locationUpdatesStatus = true; googleMap.setMyLocationEnabled(true); if (lastLocation != null) { navigateToLocation(lastLocation); } // ... } protected void stopLocationUpdates() { LocationServices.FusedLocationApi.removeLocationUpdates( googleApiClient, this ); locationUpdatesStatus = false; googleMap.setMyLocationEnabled(false); // ... }
Pentru ca impactul asupra consumului de energie să fie optim, se recomandă ca pe metodele care controlează ciclul de viață al unei aplicații Android să se gestioneze corespunzător starea transmiterii de actualizări periodice, în intervalele de timp în care aceasta nu este activă / vizibilă.
@Override protected void onStart() { super.onStart(); // ... if (googleApiClient != null && googleApiClient.isConnected() && locationUpdatesStatus) { startLocationUpdates(); } } @Override protected void onStop() { stopLocationUpdates(); // ... super.onStop(); }
Metodele care gestionează starea transmiterii de actualizări periodice cu privire la locația curentă primesc ca parametru și un obiect ascultător de tip LocationListener care notifică utilizatorul în momentul în care sunt disponibile informațiile propriu-zise: metoda onLocationChanged(Location)
, apelată în mod automat, oferă informații cu privire la poziția din momentul de timp respectiv.
@Override public void onLocationChanged(Location location) { lastLocation = location; navigateToLocation(lastLocation); }
Aplicația Android trebuie să aibă un comportament consistent în situația în care se produc modificări de configurație, astfel încât diferiții parametrii trebuie salvați și încărcați pe metodele corespunzătoare (onSaveInstanceState(Bundle)
, respectiv onRestoreInstanceState(Bundle)
):
@Override public void onSaveInstanceState(Bundle savedInstanceState) { saveValues(savedInstanceState); super.onSaveInstanceState(savedInstanceState); } protected void saveValues(Bundle state) { state.putBoolean(Constants.LOCATION_UPDATES_STATUS, locationUpdatesStatus); state.putParcelable(Constants.LAST_LOCATION, lastLocation); } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); restoreValues(savedInstanceState); } protected void restoreValues(Bundle state) { if (state.keySet().contains(Constants.LAST_LOCATION)) { lastLocation = state.getParcelable(Constants.LAST_LOCATION); } if (state.keySet().contains(Constants.LOCATION_UPDATES_STATUS)) { locationUpdatesStatus = state.getBoolean(Constants.LOCATION_UPDATES_STATUS); } }
Informațiile de interes sunt starea referitoare la transmiterea actualizărilor periodice, respectiv la cea mai recentă locație.
Acest exercițiu este opțional, cere activarea facturării (Billing) în contul google pentru care activati Geocoding API.
În Android, în contextul transmiterii de actualizări periodice cu privire la locația curentă, există posibilitatea ca un utilizator să fie notificat cu privire la acțiunile legate de o anumită zonă de restricție geografică, definită ca arie circulară, caracterizată printr-un centru, dat de coordonate GPS (latitudine / longitudine) și de o rază.
Este impusă restricția de a gestiona simultan maxim 100 de zone de restricție geografică active la un moment dat (fiecare restricție geografică are o perioadă de valabilitate).
Evenimentele generate în legătură cu o zonă de restricție geografică sunt legate de intrare, respectiv de ieșirea utilizatorului din acest spațiu, însă notificările pot fi temporizate o anumită perioadă. Acestea trebuie procesate pe un fir de execuție dedicat, a cărui execuție trebuie să fie limitată la procesările legate de un anumit tip de eceniment. În acest sens, va fi utilizat un obiect de tip IntentService
, instanțiat în momentul în care aplicația Android este creată (pe metoda onCreate()
), resursele aferente fiind eliberate în momentul în care aplicația Android este distrusă (pe metoda onDestroy()
). Acesta va fi reutilizat atât pentru operația de tip adăugare cât și pentru operația de tip ștergere a unei zone de restricție geografică.
Intent intent = new Intent(this, GeofenceTrackerIntentService.class); geofenceTrackerPendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Disponibilitatea unui serviciu care să realizeze procesări legate de zonele de restricție geografică trebuie menționată în cadrul fișierului AndroidManifest.xml
:
<manifest ...> <!-- other elements --> <application ...> <!-- other elements --> <service android:name=".service.GeofenceTrackerIntentService" android:exported="false"/> </application> </manifest>
Metoda onHandleIntent(Intent)
a serviciului lansat în execuție prin intermediul unei intenții va procesa evenimentul legat de zona de restricție geografică (GeofencingEvent) care îi este transmis:
GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE
- serviciul de restricționare geografică nu este disponibil;GEOFENCE_TOO_MANY_GEOFENCES
- au fost definite mai multe zone de restricție geografică (restricționate la maxim 100);GEOFENCE_TOO_MANY_PENDING_INTENTS
- există mai multe servicii care procesează evenimente legate de restricționarea geografică;Geofence.GEOFENCE_TRANSITION_ENTER
- utilizatorul a intrat în zona de restricție geografică;Geofence.GEOFENCE_TRANSITION_EXIT
- utilizatorul a ieșit din zona de restricție geografică.Geofence
implicat, se obține identificatorul (generat de regulă aleator), concatenându-se la mesajul ce conține detaliile tranziției;@Override protected void onHandleIntent(Intent intent) { GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); if (geofencingEvent.hasError()) { String errorMessage = null; switch(geofencingEvent.getErrorCode()) { case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE: errorMessage = Constants.GEOFENCE_NOT_AVAILABLE_ERROR; break; case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES: errorMessage = Constants.GEOFENCE_TOO_MANY_GEOFENCES_ERROR; break; case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS: errorMessage = Constants.GEOFENCE_TOO_MANY_PENDING_INTENTS_ERROR; break; default: errorMessage = Constants.GEOFENCE_UNKNOWN_ERROR; break; } Log.e(Constants.TAG, "An exception has occurred: " + errorMessage); return; } int geofenceTransition = geofencingEvent.getGeofenceTransition(); if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) { List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences(); StringBuffer transitionStringDetails = null; switch(geofenceTransition) { case Geofence.GEOFENCE_TRANSITION_ENTER: transitionStringDetails = new StringBuffer(Constants.GEOFENCE_TRANSITION_ENTER); break; case Geofence.GEOFENCE_TRANSITION_EXIT: transitionStringDetails = new StringBuffer(Constants.GEOFENCE_TRANSITION_EXIT); break; default: transitionStringDetails = new StringBuffer(Constants.GEOFENCE_TRANSITION_UNKNOWN); break; } transitionStringDetails.append(": "); for (Geofence geofence: triggeringGeofences) { transitionStringDetails.append(geofence.getRequestId() + ", "); } String transitionString = transitionStringDetails.toString(); if (transitionString.endsWith(", ")) { transitionString = transitionString.substring(0, transitionString.length() - 2); } sendNotification(transitionString); Log.i(Constants.TAG, "The geofence tansaction has been processed: " + transitionString); } else { Log.e(Constants.TAG, "An exception has occurred: " + Constants.GEOFENCE_TRANSITION_UNKNOWN + " " + geofenceTransition); } }
Transmiterea propriu-zisă a notificării implică invocarea unei activități prin intermediul unui obiect de tip PendingIntent
. Aceasta este atașată unei ierarhii, fiind transmisă prin plasarea sa pe stiva de activități.
<manifest ...> <!-- other elements --> <application ...> <!-- other elements --> <activity android:name=".graphicuserinterface.GoogleMapsGeofenceEventActivity" android:parentActivityName=".graphicuserinterface.GoogleMapsActivity"> <metadata android:name="android.support.PARENT_ACTIVITY" android:value=".graphicuserinterface.GoogleMapsActivity"/> </activity> </application> </manifest>
private void sendNotification(String notificationDetails) { Intent notificationIntent = new Intent(getApplicationContext(), GoogleMapsGeofenceEventActivity.class); notificationIntent.putExtra(Constants.NOTIFICATION_DETAILS, notificationDetails); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addParentStack(GoogleMapsGeofenceEventActivity.class); stackBuilder.addNextIntent(notificationIntent); PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setSmallIcon(R.drawable.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher)) .setColor(Color.RED) .setContentTitle(Constants.GEOFENCE_TRANSITION_EVENT) .setContentText(notificationDetails) .setContentIntent(notificationPendingIntent); builder.setAutoCancel(true); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0, builder.build()); }
Operațiile care pot fi realizate legate de o zonă de restricție geografică sunt:
1. adăugarea unei zone de restricție geografică.
O zonă de restricție geografică este construit prin intermediul unei clase Geofence.Builder, în care se specifică parametrii acesteia:
Geofence.GEOFENCE_TRANSITION_ENTER
și Geofence.GEOFENCE_TRANSITION_EXIT
).
O solicitare legată de o zonă de restricție geografică, conținută de un obiect GeofencingRequest, este construită prin apelul metodelor addGeofence(Geofence)
/ addGeofences(List<Geofence>)
, respectiv build()
din cadrul clasei ajutătoare GeofencingRequest.Builder.
Operația de adăugare este realizată prin apelul metodei addGeofences(GoogleApiClient, GeofencingRequest, PendingIntent) din clasa GeofencingApi. Rezultatul acestei operații este furnizat prin intermediul unei clase ascultător ResultCallback<T>, pentru care se implementează metoda onResult(T)
. Aceasta trebuie precizată explicit prin metoda setREsultCallback(ResultCallback<T>), aplicabilă obiectului de tip PendingResult, construit anterior.
private void addGeofence(String latitude, String longitude, String radius) { if (googleApiClient == null || !googleApiClient.isConnected()) { Toast.makeText( GoogleMapsActivity.this, "Google API Client is null or not connected!", Toast.LENGTH_SHORT ).show(); return; } if (latitude == null || latitude.isEmpty() || longitude == null || longitude.isEmpty() || radius == null || radius.isEmpty()) { Toast.makeText( GoogleMapsActivity.this, "All fields (gps coordinates, radius) should be filled!", Toast.LENGTH_SHORT ).show(); return; } geofenceList.add(new Geofence.Builder() .setRequestId(Utilities.generateGeofenceIdentifier(Constants.GEOFENCE_IDENTIFIER_LENGTH)) .setCircularRegion( Double.parseDouble(latitude), Double.parseDouble(longitude), Float.parseFloat(radius) ) .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS) .setTransitionTypes( Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT ) .build()); GeofencingRequest.Builder builder = new GeofencingRequest.Builder(); builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); builder.addGeofences(geofenceList); GeofencingRequest geofencingRequest = builder.build(); LocationServices.GeofencingApi.addGeofences( googleApiClient, geofencingRequest, geofenceTrackerPendingIntent ).setResultCallback(GoogleMapsActivity.this); }
2. ștergerea unei zone de restricție geografică:
Operația de ștergere este realizată prin apelul metodei removeGeofences(GoogleApiClient, PendingIntent) din clasa GeofencingApi, aceasta referindu-se la toate zonele de restricție geografică. Rezultatul acestei operații este furnizat prin intermediul unei clase ascultător ResultCallback<T>, pentru care se implementează metoda onResult(T)
. Aceasta trebuie precizată explicit prin metoda setREsultCallback(ResultCallback<T>), aplicabilă obiectului de tip PendingResult, construit anterior.
private void removeGeofence() { if (googleApiClient == null || !googleApiClient.isConnected()) { Toast.makeText( GoogleMapsActivity.this, "Google API Client is null or not connected!", Toast.LENGTH_SHORT ).show(); return; } latitudeEditText.setText(new String()); longitudeEditText.setText(new String()); radiusEditText.setText(new String()); geofenceList.clear(); LocationServices.GeofencingApi.removeGeofences( googleApiClient, geofenceTrackerPendingIntent ).setResultCallback(GoogleMapsActivity.this); }
Se poate observa faptul că ambele operații au nevoie de un client Google API nenul și care să fie conectat, motiv pentru care anterior sunt realizate verificările de rigoare, cu semnalarea eventualelor erori.
Zonele de restricție geografică sunt menținute în cadrul unei liste, actualizată corespunzător pentru fiecare dintre operațiile de adăugare / ștergere.
Metoda onResult(Status)
, care furnizează rezultatul operațiilor de adăugare / ștergere a unei zone de restricție geografică, conține informații suplimentare cu privire la situația curentă:
isSuccess()
- operația a fost realizată cu success sau cu eșec;getStatusCode()
- codul de stare, în situația în care s-a produs o eroare.@Override public void onResult(Status status) { if (status.isSuccess()) { geofenceStatus = !geofenceStatus; } else { String errorMessage = null; switch(status.getStatusCode()) { case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE: errorMessage = Constants.GEOFENCE_NOT_AVAILABLE_ERROR; break; case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES: errorMessage = Constants.GEOFENCE_TOO_MANY_GEOFENCES_ERROR; break; case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS: errorMessage = Constants.GEOFENCE_TOO_MANY_PENDING_INTENTS_ERROR; break; default: errorMessage = Constants.GEOFENCE_UNKNOWN_ERROR; break; } Log.e(Constants.TAG, "An exception has occurred while turning the geofencing on/off: " + status.getStatusCode() + " " + errorMessage); } }
În situația în care aplicația este întreruptă, informațiile legate de zonele geografice trebuie gestionate corespunzător, fiind recomandat ca persistența să fie realizată prin intermediul unui obiect de tip SharedPreferences
.
1. Să se acceseze Google Developer's Console, după ce a fost realizată autentificarea cu datele contului Google (nume de utilizator, parolă):
keytool
;
AndroidManifest.xml
.
Mai multe detalii pot fi obținute în secțiunea Configurare.
2. În contul Github personal, să se creeze un depozit denumit 'Laborator10'. Inițial, acesta trebuie să fie gol (nu trebuie să bifați nici adăugarea unui fișier README.md
, nici a fișierului .gitignore
sau a a fișierului LICENSE
).
3. Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la https://www.github.com/eim2017/Laborator10.
În urma acestei operații, directorul Laborator10 va trebui să se conțină directoarele labtasks
și solutions
.
student@eim2017:~$ git clone https://www.github.com/eim2017/Laborator10.git
4. Să se încarce conținutul descărcat în cadrul depozitului 'Laborator10' de pe contul Github personal.
student@eim2017:~$ cd Laborator10 student@eim2017:~/Laborator10$ git remote add Laborator10_perfectstudent https://github.com/perfectstudent/Laborator10 student@eim2017:~/Laborator10$ git push Laborator10_perfectstudent master
5. Să se configureaze mașina pe care va rula aplicația:
6. Să se importe în mediul integrat de dezvoltare Android Studio proiectul GoogleMapsPlaces
din directorul labtasks
.
Se dorește să se implementeze o aplicație care să navigheze către o locație specificată prin intermediul coordonatelor GPS (latitudine / longitudine) și pentru care se dorește plasarea unui reper pe hartă, însoțit de o denumire.
Reperele vor fi stocate în cadrul unei liste (obiect de tip Spinner
), astfel încât la selecția unui element din cadrul acesteia, se va vizualiza obiectivul geografic marcat anterior.
Se cere să se implementeze funcționalitățile pentru adăugarea unui reper și ștergerea tuturor reperelor de pe hartă:
a) pentru adăugarea unui reper:
MarkerOptions
, desemnând reperul care va fi plasat pe harta Google; MarkerOptions marker = new MarkerOptions() .position(new LatLng( Double.parseDouble(latitudeContent), Double.parseDouble(longitudeContent) )) .title(nameContent); marker.icon(BitmapDescriptorFactory.defaultMarker(Utilities.getDefaultMarker(markerTypeSpinner.getSelectedItemPosition())));
places
), notificându-se și adaptorul corespunzător obiectului de tip Spinner
(placesAdapter
) de această modificare, astfel încât acesta să fie actualizat corespunzător.b) pentru ștergerea tuturor reperelor de pe hartă:
places
), notificându-se și adaptorul corespunzător obiectului de tip Spinner
(placesAdapter
) de această modificare, astfel încât acesta să fie actualizat corespunzător.
7. Să se importe în mediul integrat de dezvoltare Android Studio proiectul GoogleMapsLocationUpdate
din directorul labtasks
.
Se dorește să se implementeze o aplicație pentru care să se implementeze posibilitatea de actualizare periodică a poziției curente pe hartă, în funcție de starea unui buton, prin care se controlează pornirea / oprirea acestui serviciu.
De asemenea, se dorește să se poată selecta tipul de hartă care să fie afișat.
Se cere să se implementeze metodele pentru pornirea și oprirea serviciului de actualizare periodică a locației de pe hartă:
a) metoda startLocationUpdates()
din clasa GoogleMapsActivity
:
requestLocationUpdates()
din clasa FusedLocationProviderApi
LocationServices.FusedLocationApi.requestLocationUpdates( googleApiClient, locationRequest, this );
locationUpdatesStatus
);setMyLocationEnabled(true)
);locationUpdateStatusButton
;latitudeEditText
, longitudeEditText
, navigateToLocationButton
.
b) metoda stopLocationUpdates()
din clasa GoogleMapsActivity
removeLocationUpdates()
din clasa FusedLocationProviderApi
LocationServices.FusedLocationApi.removeLocationUpdates( googleApiClient, this );
locationUpdatesStatus
);setMyLocationEnabled(false)
);locationUpdateStatusButton
;latitudeEditText
, longitudeEditText
, navigateToLocationButton
, acestea având un conținut vid.
8. Să se importe în mediul integrat de dezvoltare Android Studio proiectul GoogleMapsGeocoding
din directorul labtasks
.
Se dorește să se implementeze o aplicație care să realizeze procesul de codificare geografică inversă: dându-se un set de coordonate GPS, se dorește să se determine adresa poștală corespunzătoare.
Se cere să se implementeze procesul de conversie propriu-zis, în cadrul serviciului GetLocationAddressIntentService
, pe metoda onHandleIntent()
.
Geocoder
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
getFromLocation()
care primește ca parametri:Constants.NUMBER_OF_ADDRESSES
);IOException
, IllegalArgumentException
) precum și situația în care nu este furnizat nici un rezultat;getMaxAddressLineIndex()
;getAddressLine()
;handleResult()
cu codul numeric de rezultat (Constants.RESULT_SUCCESS
, Constants.RESULT_FAILURE
) și rezultatul obținut, respectiv mesajul de eroare, după caz.
9. Să se importe în mediul integrat de dezvoltare Android Studio proiectul GoogleMapsGeofencing
din directorul labtasks
.
Se dorește să se implementeze o aplicație care să monitorizeze activitatea unui dispozitiv mobil raportat la o zonă de restricție geografică.
Serviciul referitor la monitorizarea unei anumite locații poate fi activat sau dezactivat, specificându-se de fiecare dată coordonatele zonei față de care sunt realizate comparațiile.
Aplicația va afișa pe hartă în permanență locația curentă prin actualizările periodice care sunt transmise.
Notificările vor fi generate:
Detaliile cu privire la evenimentul produs vor putea fi vizualizate în cadrul unei activități dedicate.
Se cere să se analizeze evenimentul legat de zona de restricție geografică în cadrul serviciului GeofenceTrackerIntentService
, pe metoda onHandleIntent()
.
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
hasError()
), aceasta fiind tratată corespunzător (se jurnalizează eroarea și metoda se termină):GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE
GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES
GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS
.int geofenceTransition = geofencingEvent.getGeofenceTransition();
Geofence.GEOFENCE_TRANSITION_ENTER
/ Geofence.GEOFENCE_TRANSITION_EXIT
;List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
getRequestId()
);sendNotification()
).10. Să se încarce modificările realizate în cadrul depozitului 'Laborator10' de pe contul Github personal, folosind un mesaj sugestiv.
student@eim2017:~/Laborator10$ git add * student@eim2017:~/Laborator10$ git commit -m "implemented taks for laboratory 10" student@eim2017:~/Laborator10$ git push Laborator10_perfectstudent master
Location & Places
Google Maps Android API v2
Tutorial: Making your Application Location-Aware
Wei Meng LEE, Beginning Android 4 Application Development, Wiley, 2012 - capitolul 9, Location-Based Services
Reto MEIER, Professional Android for Application Development, John Wiley & Sons, 2012 - capitolul 13 Maps, Geocoding and Location-Based Services
Satya KOMATINENI, Dave MACLEAN, Pro Android 4, Apress, 2012 - capitolul 22, Exploring Maps and Location-based Services
Ronan SCHWARZ, Phil DUTSON, James STEELE, Nelson TO, Android Developer's Cookbook, Building Applications with the Android SDK, 2nd Edition, Addison Wesley, 2013 - capitolul 12, Location-Based Services
Bill PHILLIPS, Brian HARDY, Android Programming. The Big Nerd Ranch Guide, Pearson Technology Group, 2013 - capitolul 33, Tracking the Device's Location
Package com.google.android.gms.location
Android Working with Google Maps v2
Android Get Address with Street Name, City for Location with Geocoding
Setting up Google Play Services in Android Studio