External Services CSE 5236: Mobile Application Development Course Coordinator: Dr. Rajiv Ramnath Instructor: Adam C. Champion 1
External Services Viewing websites Location- and map-based functionality REST-based Web services invocation 2
Invoking Browser App Java // HelpFragment.java private void launchbrowser( String url) { Uri theuri = Uri.parse(url); Intent LaunchBrowserIntent = new Intent(Intent.ACTION_VIEW, theuri); startactivity( LaunchBrowserIntent); Kotlin // HelpFragment.kt private fun launchbrowser( url: String) { val theuri = Uri.parse(url) val LaunchBrowserIntent = Intent(Intent.ACTION_VIEW, theuri) startactivity( LaunchBrowserIntent) URL: http://en.wikipedia.org/wiki/tictactoe Note: Activity stacking due to re-launch of browser on mobile page 3
Embedded WebView - Layout <?xml version="1.0" encoding="utf-8"?> <ScrollView...> <LinearLayout......> <WebView android:id="@+id/helpwithwebview" android:layout_width="match_parent" android:layout_height="200dip" android:layout_weight="1.0"/> <Button... android:text="exit"/> </LinearLayout> </ScrollView> 4
Embedded WebView: Java // HelpWebViewFragment.java public View oncreateview(...) { View v = inflater.inflate(r.layout.fragment_help_webview,...); WebView helpinwebview = (WebView) v.findviewbyid(r.id.helpwithwebview); mprogressbar = (ProgressBar) v.findviewbyid(r.id.webviewprogress); mprogressbar.setmax(100); View buttonexit = v.findviewbyid(r.id.button_exit); buttonexit.setonclicklistener(this); Bundle extras = getactivity().getintent().getextras(); if (extras!= null) { murl = extras.getstring(arg_uri); /*... */ //... helpinwebview.setwebviewclient(new WebViewClient() { public boolean shouldoverrideurlloading(...) { return false; ); helpinwebview.setwebchromeclient(new WebChromeClient() { public void onprogresschanged(webview webview, int progress) { if (progress == 100) { mprogressbar.setvisibility(view.gone); else { mprogressbar.setvisibility(view.visible); mprogressbar.setprogress(progress); ); helpinwebview.loadurl(murl); return v; 5
Embedded WebView: Kotlin // HelpWebViewFragment.kt override fun oncreateview(... ): View? { val v = inflater.inflate(r.layout.fragment_help_webview,...) val helpinwebview = v.findviewbyid<webview>(r.id.helpwithwebview) mprogressbar = v.findviewbyid<progressbar>(r.id.webviewprogress) mprogressbar.apply { max = 100 val buttonexit = v.findviewbyid<button>(r.id.button_exit) buttonexit.setonclicklistener(this) val extras = activity.intent.extras if (extras!= null) { murl = extras.getstring(arg_uri) WebView.setWebContentsDebuggingEnabled(true) helpinwebview.settings.javascriptenabled = true helpinwebview.webviewclient = object : WebViewClient() { override fun shouldoverrideurlloading(... ): Boolean { return false helpinwebview.webchromeclient = object : WebChromeClient() { override fun onprogresschanged(... ) { if (progress == 100) { mprogressbar.visibility = View.GONE else { mprogressbar.visibility = View.VISIBLE mprogressbar.progress = progress helpinwebview.loadurl(murl) return v 6
Location-Based Applications These mix-and-match the following actions: Opening a map Invoking a geocoding service on a point of interest Navigating the map to a position or make a mapbased calculation Determining the user s geolocation from the device (latitude, longitude) 7
Additional Requirements for Maps Use built-in GoogleMap with a FragmentActivity Link against Google APIs for Android (rather than standard Android SDK) Declare the use of the Google map package: <uses-library android:name="com.google.android.maps /> Request the appropriate permissions, e.g.: <uses-permission android:name="android.permission.internet"/> <uses-permission android:name="android.permission.access_network_state"/> <uses-permission android:name="android.permission.access_coarse_location"/> <uses-permission android:name="android.permission.access_fine_location"/> <uses-permission android:name="android.permission.write_external_storage"/> <uses-permission android:name="com.google.android.providers.gsf.permission.read_gservices /> Request a Map API key Goes in AndroidManifest.xml as <meta-data android:name="com.google.android.maps.v2.api_key android:value=... /> See https://developers.google.com/maps/documentation/android/start Need OpenGL ES v2: <uses-feature android:glesversion="0x00020000 8 android:required="true"/>
Map Fragment Layout <fragment xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/map" tools:context=".mapsactivity" android:name="com.google.android.gms.maps.supportmapfragment"/> Google Maps uses Fragments UI Fragment needs to be loaded dynamically 9
Map Fragment Code: Java (1) // MapsFragment.java public class MapsFragment extends SupportMapFragment implements OnMapReadyCallback { private GoogleMap mmap; private GoogleApiClient mapiclient; private static final String[] LOCATION_PERMISSIONS = new String[] { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ; private FusedLocationProviderClient mfusedlocationproviderclient; private Location mlocation; private LatLng mdefaultlocation; private static final int REQUEST_LOCATION_PERMISSIONS = 0; private boolean mlocationpermissiongranted = false;... // MapsActivity.java just uses a SingleFragmentActivity 10
Map Fragment Code: Java (2) // MapsFragment.java (continued) public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); sethasoptionsmenu(true); mapiclient = new GoogleApiClient.Builder(getActivity()).addApi(LocationServices.API).addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { public void onconnected(@nullable Bundle bundle) { getactivity().invalidateoptionsmenu(); public void onconnectionsuspended(int i) {... ).build(); getmapasync(this); public void onresume() { super.onresume(); setupeula(); findlocation(); 11
Map Fragment Code: Java (3) // MapsFragment.java (continued) public void onstart() { //... getactivity().invalidateoptionsmenu(); mapiclient.connect(); public void onstop() { //... mapiclient.disconnect(); private void findlocation() { updatelocationui(); if (haslocationpermission()) { mfusedlocationproviderclient = LocationServices.getFusedLocationProviderClient(getActivity()); mdefaultlocation = new LatLng(40.0, -83.0); LocationRequest locationrequest = LocationRequest.create(); locationrequest.setpriority(locationrequest.priority_high_accuracy); locationrequest.setnumupdates(1); locationrequest.setinterval(0); FusedLocationProviderClient locationprovider = LocationServices.getFusedLocationProviderClient(getActivity()); Task locationresult = locationprovider.getlastlocation(); locationresult.addoncompletelistener(getactivity(), new OnCompleteListener() { public void oncomplete(@nonnull Task task) { if (task.issuccessful()) { mlocation = (Location) task.getresult(); mmap.movecamera(cameraupdatefactory.newlatlngzoom(new LatLng(mLocation.getLatitude(), mlocation.getlongitude()), 16)); 12 else { /* Disable Location */ ); // Else request permissions, end of method.
Map Fragment Code: Java (4) // MapsFragment.java (continued) private void setupeula() { msettings = getactivity().getsharedpreferences(getstring(r.string.prefs), 0); boolean iseulaaccepted = msettings.getboolean(getstring(r.string.eula_accepted_key), false); if (!iseulaaccepted) { DialogFragment euladialogfragment = new EulaDialogFragment(); euladialogfragment.show(getactivity().getsupportfragmentmanager(), "eula"); public void oncreateoptionsmenu(menu menu, MenuInflater inflater) { //... inflater.inflate(r.menu.maps_menu, menu); public boolean onoptionsitemselected(menuitem item) { switch (item.getitemid()) { case R.id.menu_showcurrentlocation: Log.d(TAG, "Showing current location"); if (haslocationpermission()) { findlocation(); else { /* Request permissions */ break; return true; 13
Map Fragment Code: Java (5) // MapsFragment.java (continued) public void onrequestpermissionsresult(...) { mlocationpermissiongranted = false; switch (requestcode) { case REQUEST_LOCATION_PERMISSIONS: { // If request is cancelled, the result arrays are empty. if (grantresults.length > 0 && grantresults[0] == PackageManager.PERMISSION_GRANTED) { mlocationpermissiongranted = true; updatelocationui(); private void updatelocationui() { if (mmap == null) { return; try { if (mlocationpermissiongranted) { /* Enable location */ else { /* Disable location, request permissions */ catch (SecurityException e) { /*... */ public void onmapready(googlemap googlemap) { mmap = googlemap; mmap.addmarker(new MarkerOptions().position(new LatLng(40.0, -83.0)).title("Ohio State University")); mmap.setmylocationenabled(true); mmap.getuisettings().setmylocationbuttonenabled(true); /*... */ private boolean haslocationpermission() { /*... */ 14
Map Fragment Code: Kotlin (1) // MapsFragment.kt class MapsFragment : SupportMapFragment(), OnMapReadyCallback { private lateinit var mmap: GoogleMap private lateinit var mapiclient: GoogleApiClient private lateinit var mfusedlocationproviderclient: FusedLocationProviderClient private var mlocation: Location? = null private var mdefaultlocation: LatLng? = null private var mlocationpermissiongranted = false private var mmapready = false companion object { private val LOCATION_PERMISSIONS = arrayof(manifest.permission.access_fine_location, Manifest.permission.ACCESS_COARSE_LOCATION) private val REQUEST_LOCATION_PERMISSIONS = 0 15
Map Fragment Code: Kotlin (2) // MapsFragment.kt (continued) override fun oncreate(savedinstancestate: Bundle?) { super.oncreate(savedinstancestate) sethasoptionsmenu(true) mapiclient = GoogleApiClient.Builder(activity).addApi(LocationServices.API).addConnectionCallbacks(object : GoogleApiClient.ConnectionCallbacks { override fun onconnected(bundle: Bundle?) { activity.invalidateoptionsmenu() override fun onconnectionsuspended(i: Int) {... ).build() getmapasync(this) override fun onresume() { super.onresume() setupeula() findlocation() 16
Map Fragment Code: Kotlin (3) // MapsFragment.kt (continued) override fun onstart() { //... activity.invalidateoptionsmenu() mapiclient.connect() override fun onstop() { //... mapiclient.disconnect() private fun findlocation() { updatelocationui() if (haslocationpermission()) { mfusedlocationproviderclient = LocationServices.getFusedLocationProviderClient(activity) mdefaultlocation = LatLng(40.0, -83.0) val locationrequest = LocationRequest.create() locationrequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY locationrequest.numupdates = 1 locationrequest.interval = 0 val locationprovider = LocationServices.getFusedLocationProviderClient(activity) val locationresult = locationprovider.lastlocation locationresult.addoncompletelistener(activity, object : OnCompleteListener<Location> { override fun oncomplete(task: Task<Location>) { if (task.issuccessful) { // Set the map's camera position to the current location of the device. mlocation = task.result as Location mmap.movecamera(cameraupdatefactory.newlatlngzoom( LatLng(mLocation!!.latitude, mlocation!!.longitude), 16f)) else { /* Disable location */ ) // Else request permissions, end of method. 17
Map Fragment Code: Kotlin (4) // MapsFragment.kt (continued) private fun setupeula() { msettings = activity.getsharedpreferences(getstring(r.string.prefs), 0) val iseulaaccepted = msettings!!.getboolean(getstring(r.string.eula_accepted_key), false) if (!iseulaaccepted) { val euladialogfragment = EulaDialogFragment() euladialogfragment.show(activity.supportfragmentmanager, "eula") override fun oncreateoptionsmenu(menu: Menu?, inflater: MenuInflater?) { super.oncreateoptionsmenu(menu, inflater) inflater?.inflate(r.menu.maps_menu, menu) override fun onoptionsitemselected(item: MenuItem?): Boolean { when (item?.itemid) { R.id.menu_showcurrentlocation -> { Log.d(TAG, "Showing current location") if (haslocationpermission()) { findlocation() else { requestpermissions(location_permissions, REQUEST_LOCATION_PERMISSIONS) return true 18
Map Fragment Code: Kotlin (5) // MapsFragment.kt (continued) override fun onrequestpermissionsresult(requestcode: Int, permissions: Array<String>, grantresults: IntArray) { mlocationpermissiongranted = false when (requestcode) { REQUEST_LOCATION_PERMISSIONS -> { // If request is cancelled, the result arrays are empty. if (grantresults.isnotempty() && grantresults[0] == PackageManager.PERMISSION_GRANTED) { mlocationpermissiongranted = true updatelocationui() private fun updatelocationui() { if (mmapready) { try { if (mlocationpermissiongranted) { mmap.ismylocationenabled = true mmap.uisettings.ismylocationbuttonenabled = true else { /* Disable location, request permissions */ catch (e: SecurityException) { Log.e("Exception: %s", e.message) /*... */ override fun onmapready(googlemap: GoogleMap) { mmap = googlemap mmap.addmarker(markeroptions().position(latlng(40.0, -83.0)).title("Ohio State University")) try { mmap.ismylocationenabled = true mmap.uisettings.ismylocationbuttonenabled = true catch (se: SecurityException) { Log.e(TAG, "Location not enabled, skipping") mmap.isbuildingsenabled = true mmap.isindoorenabled = true mmapready = true private fun haslocationpermission(): Boolean { /*... */ 19
Map Menu Layout <menu xmlns:app= "http://schemas.android.com/apk/res-auto" xmlns:android= "http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_showcurrentlocation" android:orderincategory="100" android:title="@string/show_location" android:icon="@android:drawable/ ic_menu_mylocation" app:showasaction="ifroom"/> </menu> 20
License Agreement Fragment: Java Yes, this is required public class EulaDialogFragment extends DialogFragment { public void seteulaaccepted() { SharedPreferences prefs = getactivity().getsharedpreferences( getstring(r.string.prefs), 0); SharedPreferences.Editor editor = prefs.edit(); editor.putboolean(getstring(r.string.eula_accepted_key), true).apply(); public Dialog oncreatedialog(bundle savedinstancestate) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.settitle(r.string.eula_title).setmessage(html.fromhtml(getstring(r.string.eula))).setpositivebutton(r.string.accept, new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int id) { seteulaaccepted(); ).setnegativebutton(r.string.decline, new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int which) { dialog.cancel(); getactivity().finish(); ); return builder.create(); 21
License Agreement Fragment: Kotlin class EulaDialogFragment : DialogFragment() { fun seteulaaccepted() { val prefs = activity.getsharedpreferences(getstring(r.string.prefs), 0) val editor = prefs.edit() editor.putboolean(getstring(r.string.eula_accepted_key), true).apply() override fun oncreatedialog(savedinstancestate: Bundle?): Dialog { // Use the Builder class for convenient dialog construction val builder = AlertDialog.Builder(activity) builder.settitle(r.string.about_app).setmessage(html.fromhtml(getstring(r.string.eula))).setpositivebutton(r.string.accept) { dialog, id -> seteulaaccepted().setnegativebutton(r.string.decline) { dialog, which -> dialog.cancel() activity.finish() return builder.create() 22
Location Determination and Practices Types: GPS Cellular WiFi Best practices for location-based applications: Check for connectivity Use threading to ensure responsiveness (Note: Threading built-in for many SDK components) 23
References Chapter 10: Channeling the Outside World through your Android Device from Android SDK 3 Programming for Dummies Chapters 33 34: Locations and Play Services and Maps from Android Programming: The Big Nerd Ranch Guide (3rd ed.) http://developer.android.com/reference/android/webkit/packagesummary.html http://developer.android.com/reference/android/webkit/webview.html https://developers.google.com/maps/documentation/android/start https://developer.android.com/guide/components/fragments.html https://developer.android.com/training/basics/fragments/creating.html 24
Thank You Questions and comments? 25