====== Laborator 10. Utilizarea Serviciilor de Localizare ====== 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: - **furnizarea de servicii integrate pentru localizare**, având următoarele caracteristici: nivelul de detaliu este determinat în funcție de specificațiile utilizatorului (precizie înaltă sau consum scăzut de energie), disponibilitate imediată a celei mai recente locații disponibile, optimizarea nivelului de utilizare al bateriei, luându-se în considerare solicitările existente raportat la senzorii care pot fi utilizați, flexibilitatea în gama de servicii oferite (utilizarea în interfața grafică a aplicației, cu un nivel de detaliu ridicat sau folosirea de către servicii, cu un nivel de detaliu scăzut); - **oferirea unei liste cu punctele de interes din proximitate**, raportat la un anumit domeniu (locații turistice, tipuri de organizații), acestea putând fi marcate cu ajutorul unor controale grafice dedicate; pentru fiecare punct de interes pot fi obținute informații suplimentare (descrieri, conținut multimedia), acestea putând fi furnizate și de utilizator, fiind stocate ulterior într-o bază de date Google; se poate determina astfel și locația curentă împreună cu alte repere din zonă; denumirile specifice ale locurilor respective precum și adresele corespunzătoare pot fi completate facil prin indicarea unor sugestii ce conțin variantele disponibile; - transmiterea de notificări legate de **restricția zonală** (//eng.// geofencing), prin indicarea unor coordonate aflate în proximitate anumitor locații: pot fi gestionate mai multe arealuri geografice de acet tip concomitent, fără a avea un impact semnificativ asupra consumului de energie (actualizările cu privire la locația curentă sunt realizate în funcție de distanța față de zona marcată precum și de tipul de activitate - staționar sau în mișcare: mers, alergat, în vehicul, cu bicicleta); sunt oferite informații atât cu privire la intrarea în arealul geografic cât și cu privire la ieșirea din acesta; - determinarea **activității pe care utilizatorul o desfășoară în mod curent** (staționar sau în mișcare: mers, alergat, în vehicul, cu bicicleta), fără un consum de baterie important (folosind senzori de putere mică); o astfel de funcționalitate este foarte utilă în contextul integrării cu aplicațiile care necesită actualizări cu privire la locația curentă, frecvența cu care sunt solicitate acest set de date fiind determinată de tipul de activitate aflat în desfășurare. Este recomandat ca informațiile legate de localizarea dispozitivului mobil să se realizeze folosind API-ul pus la dispoziție de Google Play Services, în detrimentul mecanismelor precedente pentru localizare (disponibile în pachetul ''android.location''). ===== Configurare ===== **1.** În cadrul [[https://console.developers.google.com|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. Trebuie să fiți autentificați folosind numele de utilizator și parola contului Google, altfel va trebui să vă creați un astfel de cont. În situația în care nu a fost creat un proiect Google API în prealabil, trebuie realizat acest lucru, prin selectarea opțiunii //Create Project...// din meniul //Select a Project//. {{ :laboratoare:laborator10:createproject01.png?nolink }} Pentru fiecare proiect trebuie să se precizeze următorii parametri: * denumirea (se generează în mod automat un identificator pentru proiect); * locația în care este găzduit motorul de aplicații; * acordul pentru transmiterea de mesaje electronice cu privire la actualizări despre funcționalități, sugestii de performanță, sondaje în vederea precizării de opinii despre serviciile oferite, oferte speciale; * acordul pentru folosirea serviciilor și a API-urilor asociate în acord cu termenii și condițiile specifice. {{ :laboratoare:laborator10:createproject02.png?nolink }} În cazul în care există un singur proiect, acesta este selectat în mod implicit. {{ :laboratoare:laborator10:createproject03.png?nolink }} * În secțiunea //API Manager// → //Overview//, în categoria //Google Maps APIs//, se accesează opțiunea //Google Maps Android API//, activându-se acest serviciu. {{ :laboratoare:laborator10:configurare01.png?nolink&600 }} {{ :laboratoare:laborator10:configurare02.png?nolink }} 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. {{ :laboratoare:laborator10:configurare09.png?nolink }} * Credențialele pot fi obținute: * prin accesarea butonului //Go to Credentials//, care implică următoarele etape:\\ **1.** indicarea tipului de API folosit (în cazul de față //Google Maps Android API//) și a locației din care va fi accesat API-ul (în cazul de față //Android//), pentru a se identifica tipul de cheie care va fi generată\\ {{ :laboratoare:laborator10:configurare04.png?nolink }}\\ **2.** specificarea unei denumiri asociate cheii pentru API;\\ {{ :laboratoare:laborator10:configurare05.png?nolink }}\\ se va indica și comanda care va trebui rulată pentru generarea cheii respective; folosind utilitarul Java ''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'').\\ Linux student@eim2016:~$ export PATH=$PATH:/usr/local/java/jdk1.8.0_92/bin student@eim2016:~$ 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_92\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\\ {{ :laboratoare:laborator10:configurare06.png?nolink }}\\ **3.** se vizualizează cheia pentru API generată care poate fi utilizată pentru accesarea serviciilor Google\\ {{ :laboratoare:laborator10:configurare07.png?nolink }}\\ În secțiunea //Credentials// pot fi vizualizate cheile pentru API generate anterior\\ {{ :laboratoare:laborator10:configurare08.png?nolink }} * în secțiunea //Credentials//, prin accesarea opțiunii //Create credentials// din care este selectat tipul de cheie necesar (în cazul de față //API key//); dacă nu se cunoaște tipul de cheie necesar, se poate selecta valoarea //Help me choose//\\ {{ :laboratoare:laborator10:configurare10.png?nolink }}\\ se indică tipul de cheie necesar, în funcție de locația pe care se găsește aplicația din care va fi accesat API-ul (în cazul de față //Android key//)\\ {{ :laboratoare:laborator10:configurare11.png?nolink }} **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. * Linux student@eim2016:~$ cd /opt/android-sdk-linux/tools student@eim2016:/opt/android-sdk-linux/tools$ sudo ./android * Windows - se deschide un terminal cu drepturi de administrator C:\Users\Student> cd "..\..\Program Files (x86)\Android\android-sdk\tools" C:\Program Files (x86)\Android\android-sdk\tools>android.bat {{ :laboratoare:laborator10:configurare12.png?nolink&600 }} Astfel, se instalează următoarele pachete: * din secțiunea //Android 4.1.2 (API 16)//, pachetul //Google APIs//; * din secțiunea //Extras//, pachetul //Google Play Services//. Biblioteca pentru accesarea funcționalității oferite de serviciul de localizare se găsește la ''/extras/google/google_play_services/libproject/google-play-services_lib''. În mediul integrat de dezvoltare Eclipse, se realizează o referință către biblioteca //Google Play Services//, astfel descărcată. * se accesează //File// → //Import// → //Android// → //Existing Android Code Into Workspace//\\ \\ {{ :laboratoare:laborator13:configurare10.png?nolink&500 }} * se indică locația unde se găsește instalată biblioteca //Google Play Services//, creându-se o copie a acestuia în spațiul de lucru (se bifează opțiunea //Copy projects into workspace//)\\ \\ {{ :laboratoare:laborator10:configurare14.png?nolink&800 }} **4.** În mediul integrat de dezvoltare preferat (Android Studio sau Eclipse), se creează un proiect corespunzător unei aplicații Android, având următoarele proprietăți: * denumirea pachetului care identifică aplicația Android în mod unic trebuie să fie aceeași cu cea precizată în momentul în care a fost generată cheia publică; * pentru **Android Studio**, trebuie ca în fișierul ''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:8.4.0' } * pentru **Eclipse**, trebuie să existe o referință către biblioteca //Google Play Services// (click dreapta pe denumirea proiectului în //Package Explorer// → //Properties//), se accesează opțiunea //Android// și se specifică referința către aceasta respectivă în secțiunea //Library// (accesând butonul //Add//)\\ \\ {{ :laboratoare:laborator15:configurare12.png?nolink&800 }} * în fișierul ''AndroidManifest.xml'' * se indică permisiunile necesare: * ''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; * pentru redarea hărților, precum și pentru operațiile de tip zoom, este necesară folosirea bibliotecii OpenGL * în secțiunea '' ... '' se indică: * cheia publică utilizată pentru accesarea funcționalității legată de serviciile de localizare * versiunea folosită pentru biblioteca Google Play Services (preluată din cadrul proiectului referit) * pentru a se asigura faptul că funcționalitatea nu va putea fi accesată decât prin intermediul aplicației Android, se va defini o permisiune, definindu-se o protecție la nivel de semnătură: * se precizează regulile pentru obfuscatorul Proguard (în fișierul ''proguard-project.txt'' din rădăcina proiectului), 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 rularea proiectului Eclipse (construirea pachetului ''.apk'') este necesară mai multă memorie ce trebuie alocată mașinii virtuale Java (datorate referinței către biblioteca Google Play Services), aceasta realizându-se prin intermediul valorilor furnizate în cadrul fișierului de configurare ''eclipse.ini'':\\ --launcher.XXMaxPermSize 1024M --launcher.XXMaxPermSize 1024m --launcher.appendVmargs -vmargs -Dosgi.requiredJavaVersion=1.7 -Xms1024m -Xmx1024m ==== Dispozitiv Fizic ==== 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//). {{ :laboratoare:laborator10:physical_device_01.png?nolink&400 }} 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. {{ :laboratoare:laborator10:physical_device_02.png?nolink&400 }} 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//). * //High accuracy// - locația este determinată folosind toate resursele disponibile (sistemul global de poziționare GPS, rețelele mobile și fără fir); * //Battery saving// - locația este determinată folosind doar informațiile furnizate de rețelele mobile și fără fir; * //Device only// - locația este determinată folosind doar informațiile furnizate de sistemul global de poziționare GPS. {{ :laboratoare:laborator10:physical_device_03.png?nolink&400 }} Se recomandă ca determinarea locației să se realizeze folosind toate resursele disponibile la un moment dat de timp pentru o precizie cât mai mare (cu un consum de energie corespunzător). ==== Dispozitiv Virtual ==== === Genymotion === 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//). {{ :laboratoare:laborator10:genymotion_gps_01.png?nolink&400 }} Alte informații care pot fi configurate sunt: * latitudinea - exprimată în grade; * longitudinea - exprimată în grade; * altitudinea - exprimată în metri; * acuratețea (nivelul de precizie) - exprimat în număr de metri cu care să se aproximeze locația exactă; * direcția - exprimată în grade. {{ :laboratoare:laborator10:genymotion_gps_02.png?nolink&400 }} De asemenea, este implementată și funcționalitatea prin intermediul căreia poate fi vizualizată poziția precizată în cadrul unei hărți Google. {{ :laboratoare:laborator10:genymotion_gps_03.png?nolink&800 }} === AVD === 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: * //Access to my location//: //On// - permite aplicațiilor Android să acceseze locația curentă, pe baza informațiilor furnizate din sursele disponibile; * //GPS Sattelites//: activat - se utilizează sistemul de poziționare globală GPS pentru determinarea locației curente; * //Wi-Fi & mobile network location//: activat - se utilizează o estimare pentru locația curentă pe baza serviciilor oferite de Google; totodată, vor fi transmise informații anonime în acest sens. {{ :laboratoare:laborator10:avd_configuration.png?nolink&600 }} Controlul poziției curente poate fi realizat prin intermediul perspectivei //Android Debug Monitor// din Android Studio sau //DDMS// din Eclipse (//Window// → //Open Perspective// → //DDMS//), 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. {{ :laboratoare:laborator10:avd_emulator_control.png?nolink&600 }} ===== Gestiunea unei Hărți Google ===== Harta Google este implementată în SDK-ul Android: * prin intermediul unei componente grafice de tipul [[https://developer.android.com/reference/com/google/android/gms/maps/MapView.html|MapView]], în acest caz fiind necesar ca metodele care controlează ciclul de viață al aplicației Android să propage evenimentele corespunzătoare și către acest element; * în cadrul unui fragment, de tipul [[http://developer.android.com/reference/com/google/android/gms/maps/MapFragment.html|MapFragment]], acesta tratând și evenimentele corespunzătoare ciclului de viață al aplicației Android. Obiectele ''MapView'' și''MapFragment'' 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ă. Pe baza controalelor grafice ''MapView'' sau ''MapFragment'', se poate obține o instanță a unui obiect [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html|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. Se recomandă să se folosească metoda asincronă [[http://developer.android.com/reference/com/google/android/gms/maps/MapFragment.html#getMapAsync%28com.google.android.gms.maps.OnMapReadyCallback%29|getMapAsync(OnMapReadyCallback)]] care garantează faptul că obiectul furnizat este nenul. Metoda ''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. Orice operație care implică un obiect de tipul ''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) { 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 [[http://developer.android.com/reference/com/google/android/gms/maps/model/MarkerOptions.html|MarkerOptions]], pentru care se pot preciza următoarele informații: * coordonatele GPS, sub forma unui obiect [[http://developer.android.com/reference/com/google/android/gms/maps/model/LatLng.html|LatLng]] (care încapsulează informații precum latitudinea și longitudinea, de tip ''double''), prin metoda [[http://developer.android.com/reference/com/google/android/gms/maps/model/MarkerOptions.html#position%28com.google.android.gms.maps.model.LatLng%29|position(LatLng)]] marker.position(new LatLng( Double.parseDouble(latitudeContent), Double.parseDouble(longitudeContent) ) ); * titlul, prin metoda [[http://developer.android.com/reference/com/google/android/gms/maps/model/MarkerOptions.html#title%28java.lang.String%29|title(String)]]; marker.title(nameContent); * pictograma, prin metoda [[http://developer.android.com/reference/com/google/android/gms/maps/model/MarkerOptions.html#icon%28com.google.android.gms.maps.model.BitmapDescriptor%29|icon(BitmapDescriptor)]], putând fi obținută prin intermediul clasei [[https://developer.android.com/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html|BitmapDescriptorFactory]]: * în formatul standard, disponibil în mai multe culori (metoda [[https://developer.android.com/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html#defaultMarker%28float%29|defaultMarker(float)]]); marker.icon(BitmapDescriptorFactory.defaultMarker(Utilities.getDefaultMarker(markerTypeSpinner.getSelectedItemPosition()))); * într-un format definit de utilizator, în funcție de resursele disponibile în cadrul aplicației Android: * [[https://developer.android.com/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html#fromAsset%28java.lang.String%29|fromAsset(String)]] - în directorul ''assets'', ce conține resurse externe; * [[https://developer.android.com/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html#fromBitmap%28android.graphics.Bitmap%29|fromBitmap(Bitmap)]] - dintr-o imagine; * [[https://developer.android.com/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html#fromFile%28java.lang.String%29|fromFile(String)]] - dintr-un fișier aflat la o cale relativă; * [[https://developer.android.com/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html#fromPath%28java.lang.String%29|fromPath(String)]] - dintr-un fișier aflat la o cale absolută; * [[https://developer.android.com/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory.html#fromResource%28int%29|fromResource(int)]] - în directorul ''drawable'', ce conține resurse care pot fi desenate. * descrierea, prin metoda [[http://developer.android.com/reference/com/google/android/gms/maps/model/MarkerOptions.html#snippet%28java.lang.String%29|snippet(String)]]; marker.snippet(descriptionContent); * vizibilitatea, prin metoda [[http://developer.android.com/reference/com/google/android/gms/maps/model/MarkerOptions.html#visible%28boolean%29|visible(boolean)]]. marker.visible(true); Un astfel de obiect este atașat unei hărți Google prin intermediul metodei [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#addMarker%28com.google.android.gms.maps.model.MarkerOptions%29|addMarker(MarkerOptions)]]. googleMap.addMarker(marker); Alte elemente grafice care pot fi vizualizate sunt: * [[https://developer.android.com/reference/com/google/android/gms/maps/model/CircleOptions.html|CircleOptions]] - formă geometrică de tip cerc; * [[https://developer.android.com/reference/com/google/android/gms/maps/model/GroundOverlayOptions.html|GroundOverlayOptions]] - suprapunerea unei alte imagini; * [[https://developer.android.com/reference/com/google/android/gms/maps/model/PolygonOptions.html|PolygonOptions]] - formă geometrică de tip poligon; * [[https://developer.android.com/reference/com/google/android/gms/maps/model/PolylineOptions.html|PolylineOptions]] - formă geometrică de tip polinie; * [[https://developer.android.com/reference/com/google/android/gms/maps/model/TileOverlayOptions.html|TileOverlay]] - suprapunerea unei alte imagini, pentru o anumită porțiune. Toate aceste controale pot fi înlăturate în momentul în care este folosită metoda [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#clear%28%29|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 [[https://developer.android.com/reference/com/google/android/gms/maps/CameraUpdate.html|CameraUpdate]]; un astfel de obiect este obținut de regulă prin metoda statică [[https://developer.android.com/reference/com/google/android/gms/maps/CameraUpdateFactory.html#newCameraPosition%28com.google.android.gms.maps.model.CameraPosition%29|newCameraPosition(CameraPosition)]] din clasa fabrică ''CameraUpdateFactory'', prin care pot fi controlate și alte proprietăți (nivelul de detaliere, vizualizarea unui anumit areal geografic); metoda [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#animateCamera%28com.google.android.gms.maps.CameraUpdate%29|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: * durata propriu-zisă a animației (exprimată în milisecunde); * un obiect ascultător (de tipul [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.CancelableCallback.html|GoogleMap.CancelableCallback]]) care indică momentul în care animația a fost terminată (''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 [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#stopAnimation%28%29|stopAnimation()]]. **3.** marcarea locației curente este realizată prin intermediul metodei [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#setMyLocationEnabled%28boolean%29|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: * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#setBuildingsEnabled%28boolean%29|setBuildingsEnabled(boolean)]] - vizualizarea exterioarelor de clădiri în format 3D; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#setContentDescription%28java.lang.String%29|setContentDescription(String)]] - descriere; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#setIndoorEnabled%28boolean%29|setIndoorEnabled(boolean)]] - vizualizarea configurațiilor interioarelor de clădiri; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#setTrafficEnabled%28boolean%29|setTrafficEnabled(boolean)]] - traficul la momentul de timp curent; 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 [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#setMapType%28int%29|setMapType()]]. Gestiunea unei hărți Google sub formă de imagine este realizată prin intermediul metodei [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#snapshot%28com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback%29|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 [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html|UiSettings]], obținut prin apelul metodei [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.html#getUiSettings%28%29|getUiSettings()]]: * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setAllGesturesEnabled%28boolean%29|setAllGesturesEnables(boolean)]] - permite sau nu toate tipurile de operații care pot fi realizate prin intermediul hărții Google; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setCompassEnabled%28boolean%29|setCompassEnabled(boolean)]] - activează sau dezactivează busola; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setIndoorLevelPickerEnabled%28boolean%29|setIndoorLevelPickerEnabled(boolean)]] - stabilește permisiunile de selectare a unui nivel în cazul unor hărți de interior; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setMapToolbarEnabled%28boolean%29| setMapToolbarEnabled(boolean)]] - indică configurările de vizualizare pentru bara de unelte; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setMyLocationButtonEnabled%28boolean%29|setMyLocationButtonEnabled(boolean)]] - referă vizualizarea controlului grafic pentru centrarea hărții Google în funcție de locația curentă; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setRotateGesturesEnabled%28boolean%29| setRotateGesturesEnabled(boolean)]] - precizează dreptul de utilizare al gesturilor legate de rotirea camerei pentru perspectiva de vizualizare; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setScrollGesturesEnabled%28boolean%29| setScrollGesturesEnabled(boolean)]] - determină posibilitatea de folosire a gesturilor pentru derulare, folosind un deget; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setTiltGesturesEnabled%28boolean%29|setTiltGesturesEnabled(boolean)]] - instaurează politica referitoare la gesturile de derulare, folosind două degete; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setZoomControlsEnabled%28boolean%29|setZoomControlsEnabled(boolean)]] - desemnează vizualizarea sau nu a unor controale grafice pentru gradul de detaliere a hărții Google; * [[https://developer.android.com/reference/com/google/android/gms/maps/UiSettings.html#setZoomGesturesEnabled%28boolean%29|setZoomGesturesEnabled(boolean)]] - controlează utilizarea gesturilor pentru vizualizarea hărții Google folosind un anumit grad de detaliere. Pentru interacțiunea cu utilizatorul au fost definite mai multe clase ascultător, ale căror metode semnalează declanșarea unor evenimente specifice: * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnCameraChangeListener.html|GoogleMap.OnCameraChangeListener]] - modificarea poziției camerei prin care este vizualizată harta Google; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnIndoorStateChangeListener.html|GoogleMap.OnIndoorStateChangeListener]] - schimbarea stării legate de vizualizarea la nivel de interior al clădirii (clădirea curentă, etajul la care se găsește utilizatorul); * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnInfoWindowClickListener.html|GoogleMap.OnInfoWindowClickListener]] - evenimente legate de fereastra ce conține informații suplimentare cu privire la o locație; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnMapClickListener.html|GoogleMap.OnMapClickListener]] - acțiune de tipul apăsare scurtă a unei poziții de pe harta Google; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnMapLoadedCallback.html|GoogleMap.OnMapLoadedCallback]] - marcarea momentului în care sunt vizibile toate componentele necesare la un moment dat; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnMapLongClickListener.html|GoogleMap.OnMapLongClickListener]] - acțiune de tipul apăsare lungă a unei poziții de pe harta Google; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnMarkerClickListener.html|GoogleMap.OnMarkerClickListener]] - descrie interacțiunea de tip apăsare a unui reper de pe harta Google; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnMarkerDragListener.html|GoogleMap.OnMarkerDragListener]] - descrie interacțiunea de tip mutare a unui reper de pe harta Google; * [[https://developer.android.com/reference/com/google/android/gms/maps/GoogleMap.OnMyLocationButtonClickListener.html|GoogleMap.OnMyLocationButtonClickListener]] - accesarea butonului pentru centrarea hărții în funcție de locația curentă. ===== Gestiunea Locației Curente printr-un Client Google API ===== API-ul Android pune la dispoziția utilizatorilor un [[http://developer.android.com/reference/com/google/android/gms/location/FusedLocationProviderApi.html|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 [[http://developer.android.com/reference/com/google/android/gms/common/api/GoogleApiClient.html|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()''. {{ :laboratoare:laborator10:clientul_google_api.png?nolink&600 }} @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): * [[http://developer.android.com/reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html|GoogleApiClient.ConnectionCallbacks]] gestionează operațiile de tip conectare / deconectare la serviciu, metodele implementate fiind: * ''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. * [[http://developer.android.com/reference/com/google/android/gms/common/api/GoogleApiClient.OnConnectionFailedListener.html|GoogleApiClient.OnConnectionFailedListener]] controlează situațiile în care nu este posibilă realizarea unei legături, metoda aferentă, ''onConnectionFailed(ConnectionResult)'', specificând rezultatul ce conține codul de eroare. Este recomandat ca metodele ascultător care controlează starea conexiunii clientului Google API la serviciul de localizare să fie implementate în aceeași clasă cu activitatea principală, aceasta implementând interfețele necesare. 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: * metoda ''connect()'' se apelează în cadrul metodei ''onStart()''; * metoda ''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 [[http://developer.android.com/reference/com/google/android/gms/location/FusedLocationProviderApi.html#getLastLocation%28com.google.android.gms.common.api.GoogleApiClient%29|getLastLocation(GoogleApiClient)]] din clasa [[http://developer.android.com/reference/com/google/android/gms/location/FusedLocationProviderApi.html|FusedLocationProviderApi]]. Se furnizează o locație al cărui grad de precizie este determinat în funcție de permisiunile pe care le deține aplicația Android. @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 [[http://developer.android.com/reference/android/location/Location.html|Location]], care conține mai multe informații, precum: * acuratețea (exprimată în metri); * altitudinea (exprimată în metri, având drept referință elipsoidul WGS 84); * palierul (exprimat în grade); * latitudine și longitudine (exprimată în grade); * furnizor; * viteză (exprimată în m/s); * momentul de timp (exprimat în milisecunde, raportat la 1 ianuarie 1970). 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 [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html|LocationRequest]]: * [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setExpirationDuration%28long%29|setExpiration(long)]] - durata solicitării (exprimată în milisecunde), după care nu mai sunt furnizate actualizări cu privire la locația curentă (momentul de timp la care sunt raportate este cel în care este apelată metoda, nu cel în care este realizată solicitarea propriu-zisă); * [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setExpirationTime%28long%29|setExpirationTime(long)]] - momentul de timp după care nu mai sunt furnizate actualizări cu privire la locația curentă, exprimat în milisecunde raportat la perioada în care dispozitivul mobil a fost pornit; * [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setInterval%28long%29|setInterval(long)]] - indică intervalul de timp (exprimat în milisecunde) la care se dorește să se primească actualizările cu privire la locația curentă; acesta poate fi însă: * mai mic, dacă dispozitivul mobil are probleme legate de conectivitate; * mai mare, dacă există alte aplicații care au stabilit alte rate de transfer. * [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setFastestInterval%28long%29|setFastestInterval(long)]] - precizează intervalul la care aplicația Android poate să gestioneze actualizările cu privire la locația curentă, în situația în care există și alte aplicații care au specificat alte rate de transfer, pentru a preîntâmpina situații cum ar fi imposibilitatea de actualizare corespunzătoare a interfeței grafice sau lipsa de spațiu de stocare disponibil pentru informațiile respective; Frecvența cu care sunt transmise actualizările periodice cu privire la modificarea locației curentă este dată de cea mai rapidă rată de transfer specificată de toate aplicațiile care accesează o astfel de funcționalitate. În situația în care aplicația Android realizează procesări complexe, care implică o perioadă de timp considerabilă, este recomandat ca valoarea transmisă metodei ''setFastestInterval()'' să fie corespunzătoare, astfel încât să nu fie furnizate valori care nu pot fi utilizate. * [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setMaxWaitTime%28long%29|setMaxWaitTime(long)]] - specifică perioada de așteptare maximă pentru transmiterea actualizărilor periodice referitoare la locația curentă; astfel, mai multe informații de acest tip pot fi livrate împreună, cu frecvența indicată de acest interval, optimizând consumul de baterie (actualizările periodice sunt primite la intervale de maxWaitTime, numărul de seturi de date fiind maxWaitTime / interval); * [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setNumUpdates%28int%29|setNumUpdates(int)]] - determină numărul de actualizări necesare, în caz contrar fiind furnizate valori de acest tip continuu, între apelurile metodelor ''requestLocationUpdates()'' și ''removeLocationUpdates()''; * [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setPriority%28int%29|setPriority()]] - stabilește prioritatea solicitării, oferind informații cu privire la furnizorul de servicii care va fi utilizat: * ''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); * [[http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setSmallestDisplacement%28float%29|setSmallestDisplacement(int)]] - indică distanța minimă dintre locații pentru care se primește actualizare periodică (implicit, are valoarea 0). 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: * [[http://developer.android.com/reference/com/google/android/gms/location/FusedLocationProviderApi.html#requestLocationUpdates%28com.google.android.gms.common.api.GoogleApiClient,%20com.google.android.gms.location.LocationRequest,%20com.google.android.gms.location.LocationListener%29|requestLocationUpdates(GoogleApiClient, LocationRequest, LocationListener)]] - pornește transmitersa actualizărilor periodice; * [[http://developer.android.com/reference/com/google/android/gms/location/FusedLocationProviderApi.html#removeLocationUpdates%28com.google.android.gms.common.api.GoogleApiClient,%20com.google.android.gms.location.LocationListener%29|removeLocationUpdates(GoogleApiClient, LocationListener)]] - oprește transmiterea actualizărilor periodice. Î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 [[http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html|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. ===== Codificare Geografică Inversă (Geocoding) ===== În Android, clasa [[http://developer.android.com/reference/android/location/Geocoder.html|Geocoder]] permite realizarea de conversii dintre coordonate GPS (latitudine / longitudine) și adresa poștală, operație denumită codificare geografică inversă. Metodele ''getFromLocation()'' / ''getFromLocationName()'', disponibile în mai multe forme, furnizează o listă de obiecte de tip [[http://developer.android.com/reference/android/location/Address.html|Address]], care încapsulează, pe lângă datele propriu-zise, dispuse sub formă de rânduri distincte și alte informații precum latitudine, longitudine, localitate, cod poștal, telefon, URL, împrejurimi: * [[http://developer.android.com/reference/android/location/Geocoder.html#getFromLocation%28double,%20double,%20int%29|getFromLocation(double, double, int)]] - primește ca parametrii coordonatele GPS (latitudinea / longitudinea); * [[http://developer.android.com/reference/android/location/Geocoder.html#getFromLocationName%28java.lang.String,%20int,%20double,%20double,%20double,%20double%29|getFromLocationName(String, int, double, double, double, double)]] - primește ca parametrii o descriere a locației precum și o zonă geografică descrisă prin coordonatele GPS (latitudine / longitudine) ale punctelor stânga sus - dreapta jos; * [[http://developer.android.com/reference/android/location/Geocoder.html#getFromLocationName%28java.lang.String,%20int%29|getFromLocationName(String, int)]] - primește ca parametru o descriere a locației. Toate aceste metode primesc un parametru care indică numărul de rezultate care se doresc a fi furnizate. De regulă, acesta are valoarea 1. Metodele care realizează codificarea geografică inversă sunt sincrone, iar procesările pe care le realizează pot să dureze un interval de timp considerabil. Din acest motiv, este recomandat ca invocarea acestora să nu se facă pe firul de execuție al interfeței grafice (principal) întrucât poate afecta experiența utilizatorului, ci pe un fir de execuție care rulează în fundal, de tip [[http://developer.android.com/reference/android/app/IntentService.html|IntentService]] (utilizarea clasei ''AsyncTask'' nu este indicată în această situație întrucât comportamentul său în cazul producerii unor întreruperi nu corespunde funcționalității dorite). Un astfel de obiect pornește în momentul în care este necesar să se realizeze o operație (fiind invocat prin intermediul unei intenții, la care pot fi atașate informații suplimentare), realizată pe un fir de execuție dedicat, fiind oprit în momentul în care nu mai este necesar să realizeze alte procesări. Acest serviciu asociat unei intenții trebuie să fie specificat în fișierul ''AndroidManifest.xml'', în secțiunea '' ... '': Nu este necesar să se specifice și un filtru de intenții, de vreme ce serviciul va fi lansat în execuție explicit, prin transmiterea în cadrul intenției corespunzătoare a denumirii clasei care îl implementează. Obiectele care vor fi transmise serviciului prin intermediul intenției sunt: * un obiect de tip [[http://developer.android.com/reference/android/os/ResultReceiver.html|ResultReceiver]], prin intermediul căruia este furnizat rezultatul, atunci când acesta este disponibil; private class AddressResultReceiver extends ResultReceiver { public AddressResultReceiver(Handler handler) { super(handler); } @Override protected void onReceiveResult(int resultCode, Bundle bundle) { String address = bundle.getString(Constants.RESULT); addressTextView.setText(address); switch(resultCode) { case Constants.RESULT_SUCCESS: Toast.makeText(GoogleMapsActivity.this, "An address was found", Toast.LENGTH_SHORT).show(); break; case Constants.RESULT_FAILURE: Toast.makeText(GoogleMapsActivity.this, "An address was not found", Toast.LENGTH_SHORT).show(); break; } getAddressLocationStatus = false; getLocationAddressButton.setEnabled(true); } } Se observă că atunci când rezultatul este disponibil, se apelează în mod automat metoda ''onReceiveResult()'' care primește ca parametrii un cod de rezultat numeric (succes sau eșec, definit de utilizator) și un obiect de tip ''Bundle'' în care sunt plasate informații suplimentare (adresa propriu-zisă sau mesajul de eroare - în funcție de rezultat -, vizualizată într-un control grafic). * locația care se dorește a fi rezolvată (obiect de tip ''Location'', incluzând informații de tip latitudine și longitudine). Mecanismul prin care se pornește un serviciu prin intermediul unei intenții este similar cu cel prin care se pornește o activitate prin intermediul unei intenții: - se instanțiază un obiect de tip ''Intent'' specificând clasa corespunzătoare serviciului care se dorește a fi lansat în execuție; - se plasează informațiile în obiectul ''Bundle'' disponibil în secțiunea ''extra''. Intent intent = new Intent(this, GetLocationAddressIntentService.class); intent.putExtra(Constants.RESULT_RECEIVER, addressResultReceiver); intent.putExtra(Constants.LOCATION, lastLocation); startService(intent); Procesarea pe care o realizează serviciul lansat în execuție prin intermediul unei intenții este plasată în cadrul metodei ''onHandleIntent(Intent)'', apelată în mod automat. @Override protected void onHandleIntent(Intent intent) { String errorMessage = null; resultReceiver = intent.getParcelableExtra(Constants.RESULT_RECEIVER); if (resultReceiver == null) { errorMessage = "No result receiver was provided to handle the information"; Log.e(Constants.TAG, "An exception has occurred: " + errorMessage); return; } Location location = intent.getParcelableExtra(Constants.LOCATION); if (location == null) { errorMessage = "No location data was provided"; Log.e(Constants.TAG, "An exception has occurred: " + errorMessage); handleResult(Constants.RESULT_FAILURE, errorMessage); return; } Geocoder geocoder = new Geocoder(this, Locale.getDefault()); List
addressList = null; try { addressList = geocoder.getFromLocation( location.getLatitude(), location.getLongitude(), Constants.NUMBER_OF_ADDRESSES); } catch (IOException ioException) { errorMessage = "The background geocoding service is not available"; Log.e(Constants.TAG, "An exception has occurred: " + ioException.getMessage()); } catch (IllegalArgumentException illegalArgumentException) { errorMessage = "The latitude / longitude values that were provided are invalid " + location.getLatitude() + " / " + location.getLongitude(); Log.e(Constants.TAG, "An exception has occurred: " + illegalArgumentException.getMessage()); } if (errorMessage != null && !errorMessage.isEmpty()) { handleResult(Constants.RESULT_FAILURE, errorMessage); return; } if (addressList == null || addressList.isEmpty()) { errorMessage = "The geocoder could not find an address for the given latitude / longitude"; Log.e(Constants.TAG, "An exception has occurred: " + errorMessage); handleResult(Constants.RESULT_FAILURE, errorMessage); return; } StringBuffer result = new StringBuffer(); for (Address address: addressList) { for (int k = 0; k < address.getMaxAddressLineIndex(); k++) { result.append(address.getAddressLine(k) + System.getProperty("line.separator")); } result.append(System.getProperty("line.separator")); } handleResult(Constants.RESULT_SUCCESS, result.toString()); } Operațiile realizate de serviciu, pe firul de execuție separat, sunt: - preluarea informațiilor necesare (obiect de tip ''ResultReceiver'', locația care se dorește a fi rezolvată) printr-un obiect ''Bundle'', din cadrul secțiunii ''extra'' a intenției prin care a fost invocat; în situația în care acestea nu pot fi obținute, se generează un mesaj de eroare; - instanțierea unui obiect de tip ''Geocoder'', folosind contextul (serviciul) și un obiect [[http://developer.android.com/reference/java/util/Locale.html|Locale]], care conține informații cu privire la modul de prezentare a unor informații în funcție de zona geografică; - invocarea metodelor ''getFromLocation()'' / ''getFromLocationName()'' (furnizând informațiile necesare ca parametri) și gestionând corespunzător tipurile de eroare ce pot fi generate: - serviciul Geocoding nu este disponibil (se aruncă o excepție de tip ''IOException''); - informațiile cu privire la coordonatele GPS (latitudine / longitudine) nu sunt corecte (se aruncă o excepție de tipul ''IllegalArgumentException''); - se semnalează situația în care nu a putut fi identificată nici o adresă poștală asociată datelor specificate; - pentru fiecare adresă poștală furnizată, se concatenează rândurile distincte: - numărul de rânduri conținute este întors de metoda [[http://developer.android.com/reference/android/location/Address.html#getMaxAddressLineIndex%28%29|getMaxAddressLineIndex()]]; - un rând de la o anumită poziție este dat de metoda [[http://developer.android.com/reference/android/location/Address.html#getAddressLine%28int%29|getAddressLine(int)]]; - se transmite codul de rezultat (numeric - succes sau eșec) precum și rezultatul propriu-zis (adresa sau mesajul de eroare) către obiectul de tip ''ResultReceiver'', prin intermediul metodei [[http://developer.android.com/reference/android/os/ResultReceiver.html#send%28int,%20android.os.Bundle%29|send(int, Bundle)]] care face ca la nivelul acestui obiect să se apeleze în mod automat metoda ''onReceiveResult()''; informațiile vor fi plasate în cadrul unui obiect ''Bundle'', ca pereche (cheie, valoare): private void handleResult(int resultCode, String message) { Bundle bundle = new Bundle(); bundle.putString(Constants.RESULT, message); resultReceiver.send(resultCode, bundle); } ===== Implementarea Zonelor de Restricție Geografică (Geofencing) ===== Î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'': Metoda ''onHandleIntent(Intent)'' a serviciului lansat în execuție prin intermediul unei intenții va procesa evenimentul legat de zona de restricție geografică ([[https://developer.android.com/reference/com/google/android/gms/location/GeofencingEvent.html|GeofencingEvent]]) care îi este transmis: - se verifică situația în care evenimentul legat de zona de restricție geografică conține o eroare (informație furnizată de metoda [[https://developer.android.com/reference/com/google/android/gms/location/GeofencingEvent.html#hasError%28%29|hasError()]]), aceasta fiind procesată în mod corespunzător (codul de eroare este furnizat de metoda [[https://developer.android.com/reference/com/google/android/gms/location/GeofencingEvent.html#getErrorCode%28%29|getErrorCode()]]: - ''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ă; - alt tip de eroare. - este obținut tipul de tranziție, prin intermediul metodei [[https://developer.android.com/reference/com/google/android/gms/location/GeofencingEvent.html#getGeofenceTransition%28%29|getGeofenceTransition()]] - ''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ă. - se obține lista cu zonele de restricție geografică care au determinat declanșarea evenimentelor respective, prin apelarea metodei [[https://developer.android.com/reference/com/google/android/gms/location/GeofencingEvent.html#getTriggeringGeofences%28%29|getTriggeringGeofences()]]; pentru fiecare obiect ''Geofence'' implicat, se obține identificatorul (generat de regulă aleator), concatenându-se la mesajul ce conține detaliile tranziției; - este creată o notificare, la accesarea căreia se va lansa o activitate Android în care va putea fi vizualizat mesajul cu 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 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. 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 [[https://developer.android.com/reference/com/google/android/gms/location/Geofence.Builder.html|Geofence.Builder]], în care se specifică parametrii acesteia: * [[https://developer.android.com/reference/com/google/android/gms/location/Geofence.Builder.html#setCircularRegion%28double,%20double,%20float%29|setCircularRegion(double, double, float)]] - se indică coordonatele zonei de restricție geografice: * centru - latitudine + longitudine; * raza; * [[https://developer.android.com/reference/com/google/android/gms/location/Geofence.Builder.html#setExpirationDuration%28long%29|setExpirationDuration(long)]] - se precizează durata de timp (exprimată în milisecunde) după care zona de restricția geografică nu va mai fi activă; * [[https://developer.android.com/reference/com/google/android/gms/location/Geofence.Builder.html#setLoiteringDelay%28int%29|setLoiteringDelay(int)]] - se exprimă o durată de timp (exprimată în milisecunde) în care este temporizată transmiterea de notificări, în situația în care se produc evenimente legate de zona de restricție geografică, cu durata mai mică decât cea indicată; * [[https://developer.android.com/reference/com/google/android/gms/location/Geofence.Builder.html#setNotificationResponsiveness%28int%29|setNotificationResponsiveness(int)]] - se stabilește durata de timp (exprimată în milisecunde) după care va fi transmisă notificarea; * [[https://developer.android.com/reference/com/google/android/gms/location/Geofence.Builder.html#setRequestId%28java.lang.String%29|setRequestId(String)]] - se asociază un identificator unic, prin care zona de restricție geografică va putea fi referită în cadrul aplicației Android; * [[https://developer.android.com/reference/com/google/android/gms/location/Geofence.Builder.html#setTransitionTypes%28int%29|setTransitionTypes(int)]] - se asociază tipurile de tranziții pentru care se vor transmite notificări (de regulă, ''Geofence.GEOFENCE_TRANSITION_ENTER'' și ''Geofence.GEOFENCE_TRANSITION_EXIT''). O solicitare legată de o zonă de restricție geografică, conținută de un obiect [[http://developer.android.com/reference/com/google/android/gms/location/GeofencingRequest.html|GeofencingRequest]], este construită prin apelul metodelor ''addGeofence(Geofence)'' / ''addGeofences(List)'', respectiv ''build()'' din cadrul clasei ajutătoare [[http://developer.android.com/reference/com/google/android/gms/location/GeofencingRequest.Builder.html|GeofencingRequest.Builder]]. Operația de adăugare este realizată prin apelul metodei [[http://developer.android.com/reference/com/google/android/gms/location/GeofencingApi.html#addGeofences%28com.google.android.gms.common.api.GoogleApiClient,%20com.google.android.gms.location.GeofencingRequest,%20android.app.PendingIntent%29|addGeofences(GoogleApiClient, GeofencingRequest, PendingIntent)]] din clasa [[http://developer.android.com/reference/com/google/android/gms/location/GeofencingApi.html|GeofencingApi]]. Rezultatul acestei operații este furnizat prin intermediul unei clase ascultător [[http://developer.android.com/reference/com/google/android/gms/common/api/ResultCallback.html|ResultCallback]], pentru care se implementează metoda ''onResult(T)''. Aceasta trebuie precizată explicit prin metoda [[http://developer.android.com/reference/com/google/android/gms/common/api/PendingResult.html#setResultCallback%28com.google.android.gms.common.api.ResultCallback%3CR%3E%29|setREsultCallback(ResultCallback)]], aplicabilă obiectului de tip [[http://developer.android.com/reference/com/google/android/gms/common/api/PendingResult.html|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 [[https://developer.android.com/reference/com/google/android/gms/location/GeofencingApi.html#removeGeofences%28com.google.android.gms.common.api.GoogleApiClient,%20android.app.PendingIntent%29|removeGeofences(GoogleApiClient, PendingIntent)]] din clasa [[http://developer.android.com/reference/com/google/android/gms/location/GeofencingApi.html|GeofencingApi]], aceasta referindu-se la toate zonele de restricție geografică. Rezultatul acestei operații este furnizat prin intermediul unei clase ascultător [[http://developer.android.com/reference/com/google/android/gms/common/api/ResultCallback.html|ResultCallback]], pentru care se implementează metoda ''onResult(T)''. Aceasta trebuie precizată explicit prin metoda [[http://developer.android.com/reference/com/google/android/gms/common/api/PendingResult.html#setResultCallback%28com.google.android.gms.common.api.ResultCallback%3CR%3E%29|setREsultCallback(ResultCallback)]], aplicabilă obiectului de tip [[http://developer.android.com/reference/com/google/android/gms/common/api/PendingResult.html|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''. - atunci când aplicația Android nu mai este vizibilă, se salvează datele și sunt șterse toate zonele de restricție geografică, astfel încât să nu mai fie transmise notificări; - atunci când aplicația Android este vizibilă, se încarcă datele și sunt adăugate toate zonele de restricție geografică, în cazul în care acestea au fost definite anterior. ===== Activitate de Laborator ===== **1.** Să se acceseze [[https://console.developers.google.com|Google Developer's Console]], după ce a fost realizată autentificarea cu datele contului Google (nume de utilizator, parolă): * se obține un număr de proiect (dacă această operație nu a fost realizată anterior); * se activează serviciul //Google Maps Android API//; * se generează o semnătură digitală de tip SHA-1 folosind utilitarul Java ''keytool''; * se creează o cheie publică pentru transmiterea de mesaje provenind de la un dispozitiv mobil, pe baza semnăturii digitale și a pachetului care identifică în mod unic aplicația respectivă. Pentru toate aplicațiile Android va trebui completată cheia publică în fișierul ''AndroidManifest.xml''. Mai multe detalii pot fi obținute în secțiunea [[:laboratoare:laborator10#configurare|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/eim2016/Laborator10|]]. În urma acestei operații, directorul Laborator10 va trebui să se conțină directoarele ''labtasks'' și ''solutions''. student@eim2016:~$ git clone https://www.github.com/eim2016/Laborator10 **4.** Să se încarce conținutul descărcat în cadrul depozitului 'Laborator10' de pe contul Github personal. student@eim2016:~$ cd Laborator10 student@eim2016:~/Laborator10$ git remote add Laborator10_perfectstudent https://github.com/perfectstudent/Laborator10 student@eim2016:~/Laborator10$ git push Laborator10_perfectstudent master **5.** Să se configureaze mașina pe care va rula aplicația: * în cazul în care se utilizează un emulator, se configurează astfel încât acesta să aibă instalat Google Play Services; * în cazul în care se utilizează un dispozitiv fizic, acesta trebuie să ruleze un sistem de operare Android cu o versiune ulterioară 2.2, având asociat un cont Google. În situația în care nu se reușește configurarea unui dispozitiv virtual, se poate folosi [[http://eim2016.andreirosucojocaru.ro/resources/Genymobile.tar.gz|o imagine Nexus S (API 16)]] care trebuie plasată în directorul ''$HOME''. **6.** Să se importe în mediul integrat de dezvoltare preferat (Android Studio sau Eclipse) proiectele ''GoogleMapsPlaces'' și ''google-play-service_lib'' (doar pentru Eclipse) 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. {{ :laboratoare:laborator10:google_maps_places.png?nolink&400 }} 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: - se obțin informațiile legate de latitudine, longitudine și denumire, din câmpurile text corespunzătoare și se verifică să fie completate (în caz contrar generându-se un mesaj de eroare); - se realizează navigarea către locația respectivă; - se instanțiază un obiect de tip ''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()))); - se adaugă reperul pe harta Google; - se adaugă reperul în lista de locații (''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ă: - se verifică să fie completate repere pe harta Google (în caz contrar generându-se un mesaj de eroare); - se șterg toate reperele pe harta Google; - se șterg toate reperele din lista de locații (''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 preferat (Android Studio sau Eclipse) proiectele ''GoogleMapsLocationUpdate'' și ''google-play-service_lib'' (doar pentru Eclipse) 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. - când serviciul este activat, se vizualizează pe hartă doar locația curentă, completându-se în mod automat informații precum latitudinea și longitudinea, actualizându-se corespunzător poziția; - când serviciul este dezactivat, utilizatorul are posibilitatea de a controla poziția care se vizualizează pe hartă. De asemenea, se dorește să se poată selecta tipul de hartă care să fie afișat. {{ :laboratoare:laborator10:google_maps_location_update_01.png?nolink&400 }} {{ :laboratoare:laborator10:google_maps_location_update_02.png?nolink&400 }} 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'': - se apelează metoda ''requestLocationUpdates()'' din clasa ''FusedLocationProviderApi'' LocationServices.FusedLocationApi.requestLocationUpdates( googleApiClient, locationRequest, this ); - se actualizează starea serviciului de transmitere periodice a locației curente (''locationUpdatesStatus''); - se vizualizează poziția curentă pe harta Google (se apelează metoda ''setMyLocationEnabled(true)''); - se modifică textul și culoarea butonului ''locationUpdateStatusButton''; - se navighează la locația curentă; - se dezactivează controalele grafice ''latitudeEditText'', ''longitudeEditText'', ''navigateToLocationButton''. **b)** metoda ''stopLocationUpdates()'' din clasa ''GoogleMapsActivity'' - se apelează metoda ''removeLocationUpdates()'' din clasa ''FusedLocationProviderApi'' LocationServices.FusedLocationApi.removeLocationUpdates( googleApiClient, this ); - se actualizează starea serviciului de transmitere periodice a locației curente (''locationUpdatesStatus''); - nu se vizualizează poziția curentă pe harta Google (se apelează metoda ''setMyLocationEnabled(false)''); - se modifică textul și culoarea butonului ''locationUpdateStatusButton''; - se activează controalele grafice ''latitudeEditText'', ''longitudeEditText'', ''navigateToLocationButton'', acestea având un conținut vid. **8.** Să se importe în mediul integrat de dezvoltare preferat (Android Studio sau Eclipse) proiectele ''GoogleMapsGeocoding'' și ''google-play-service_lib'' (doar pentru Eclipse) 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. {{ :laboratoare:laborator10:google_maps_geocoding.png?nolink&400 }} Se cere să se implementeze procesul de conversie propriu-zis, în cadrul serviciului ''GetLocationAddressIntentService'', pe metoda ''onHandleIntent()''. - se instanțiază un obiect de tip ''Geocoder'' Geocoder geocoder = new Geocoder(this, Locale.getDefault()); - se obține lista de adrese prin invocarea metodei ''getFromLocation()'' care primește ca parametri: - latitudinea; - longitudinea; - numărul de adrese întoarse (''Constants.NUMBER_OF_ADDRESSES''); - se tratează corespunzător tipurile de execepții ce pot fi generate (''IOException'', ''IllegalArgumentException'') precum și situația în care nu este furnizat nici un rezultat; - se parcurge lista de adrese: pentru fiecare adresă în parte se concatenează rândurile distincte, concatenându-se toate rezultatele obținute; - numărul de linii dintr-o adresă este furnizat de metoda ''getMaxAddressLineIndex()''; - un rând de la o anumită poziție se obține prin intermediul metodei ''getAddressLine()''; - se transmite rezultatul către activitatea principală (se apelează metoda ''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 preferat (Android Studio sau Eclipse) proiectele ''GoogleMapsGeofencing'' și ''google-play-service_lib'' (doar pentru Eclipse) 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. {{ :laboratoare:laborator10:google_maps_geofencing_01.png?nolink&400 }} Notificările vor fi generate: - când utilizatorul intră în zona de restricție geografică\\ \\ {{:laboratoare:laborator10:google_maps_geofencing_02.png?nolink&300}} {{:laboratoare:laborator10:google_maps_geofencing_03.png?nolink&300}} - când utilizatorul iese din zona de restricție geografică\\ \\ {{:laboratoare:laborator10:google_maps_geofencing_04.png?nolink&300}} {{:laboratoare:laborator10:google_maps_geofencing_05.png?nolink&300}} 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()''. - se obține evenimentul legat de zona de restricție geografică, din cadrul intenției cu care a fost lansat în execuție serviciul GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); - se verifică dacă evenimentul legat de zona de restricție geografică conține vreo eroare (prin apelul metodei ''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''. - se obține tipul de tranziție geografică int geofenceTransition = geofencingEvent.getGeofenceTransition(); - se construiește un mesaj explicativ conținând - tipul tranziției: ''Geofence.GEOFENCE_TRANSITION_ENTER'' / ''Geofence.GEOFENCE_TRANSITION_EXIT''; - identificatorii restricțiilor legate de poziționarea geografică: - se obține lista tuturor restricțiilor legate de poziționarea geografică List triggeringGeofences = geofencingEvent.getTriggeringGeofences(); - se parcurge lista și pentru fiecare obiect se obține identificatorul unic (''getRequestId()''); - se transmite mesajul explicativ prin intermediul unei notificări (ca parametru al metodei ''sendNotification()''). **10.** Să se încarce modificările realizate în cadrul depozitului 'Laborator10' de pe contul Github personal, folosind un mesaj sugestiv. student@eim2016:~/Laborator10$ git add * student@eim2016:~/Laborator10$ git commit -m "implemented taks for laboratory 10" student@eim2016:~/Laborator10$ git push Laborator10_perfectstudent master ===== Resurse Utile ===== [[http://developer.android.com/google/play-services/location.html|Location & Places]]\\ [[https://developers.google.com/maps/documentation/android/|Google Maps Android API v2]]\\ [[http://developer.android.com/training/location/index.html|Tutorial: Making your Application Location-Aware]]\\ [[http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1118199545.html|Wei Meng LEE, Beginning Android 4 Application Development, Wiley, 2012]] - capitolul 9, //Location-Based Services//\\ [[http://www.amazon.com/Professional-Android-4-Application-Development/dp/1118102274|Reto MEIER, Professional Android for Application Development, John Wiley & Sons, 2012]] - capitolul 13 //Maps, Geocoding and Location-Based Services//\\ [[http://www.apress.com/9781430239307|Satya KOMATINENI, Dave MACLEAN, Pro Android 4, Apress, 2012]] - capitolul 22, //Exploring Maps and Location-based Services//\\ [[http://books.google.ro/books/about/The_Android_Developer_s_Cookbook.html?id=Y4JR2yI2Fo0C&redir_esc=y|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//\\ [[http://www.bignerdranch.com/we-write/android-programming/|Bill PHILLIPS, Brian HARDY, Android Programming. The Big Nerd Ranch Guide, Pearson Technology Group, 2013]] - capitolul 33, //Tracking the Device's Location//\\ [[http://developer.android.com/reference/com/google/android/gms/location/package-summary.html|Package com.google.android.gms.location]]\\ [[http://www.androidhive.info/2013/08/android-working-with-google-maps-v2/|Android Working with Google Maps v2]]\\ [[http://javapapers.com/android/android-get-address-with-street-name-city-for-location-with-geocoding/|Android Get Address with Street Name, City for Location with Geocoding]]\\ [[https://developers.google.com/android/guides/setup#add_google_play_services_to_your_project|Setting up Google Play Services in Android Studio]]