Differences

This shows you the differences between two versions of the page.

Link to this comparison view

smd:laboratoare:old:03 [2019/03/06 14:52] (current)
adriana.draghici created
Line 1: Line 1:
 +===== Labs 03,04. Connectivity =====
 +
 +=== Task 1 - Downloading remote content (4p) ===
 +Create a new project with a blank main Activity. Change the layout of the Activity (activity_main.xml) to
 +
 +<​code>​
 +<?xml version="​1.0"​ encoding="​utf-8"?>​
 +<​android.support.constraint.ConstraintLayout
 +    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"​
 +    tools:​context="​com.softwareup.laborator03.MainActivity">​
 +
 +    <​ScrollView
 +        android:​layout_width="​match_parent"​
 +        android:​layout_height="​match_parent">​
 +
 +        <​LinearLayout
 +            android:​layout_width="​match_parent"​
 +            android:​layout_height="​match_parent"​
 +            android:​orientation="​vertical">​
 +
 +            <!-- Add all elements here -->
 +
 +        </​LinearLayout>​
 +    </​ScrollView>​
 +</​android.support.constraint.ConstraintLayout>​
 +</​code>​
 +
 +Add in the LinearLayout tag an EditText, a Button and a TextView to the Activity'​s layout. When the user enters an URL into the EditText and clicks the Button, download the remote HTTP content at that URL and display it into the TextView.
 +
 +By design, the Android framework will not allow long-running or potentially blocking operations (such as network operations) to be carried out on the main UI thread. As such, within the onClick() method of the Button we want to start a new thread in which to download the HTTP data. 
 +
 +The simplest solution would be using a Thread instance. However, we want to modify a UI element (the TextView) right after the thread has finished downloading data, which cannot be done from a Thread'​s run() method. For such a scenario, the framework provides the AsyncTask class. ​
 +
 +When defining the AsyncTask instance we can provide 3 parameters. Since we want to give our instance a String (the URL) to work with and have it return another String (the downloaded content) the first and third parameters will be String while the second one can be left Void: 
 +<​code>​
 +button.setOnClickListener(new OnClickListener() {
 +    @Override
 +    public void onClick(View v){
 +        AsyncTask<​String,​ Void, String> task = new AsyncTask<​String,​ Void, String>​(){
 +        ...
 +        };
 +    }
 +});
 +</​code>​
 +
 +The most important method within the AsyncTask is **doInBackground()**,​ which is executed on another thread. Since we have defined the AsyncTask as having a String as the first and third parameters, the signature of the doInBackground() method will be:
 +<​code>​
 +@Override
 +protected String doInBackground(String... params){
 +...
 +}
 +</​code>​
 +
 +In order to retrieve HTTP data, we can use the HttpURLConnection API, using params[0] as the remote URL:
 +<​code>​
 +URL url = new URL(params[0]);​
 +HttpURLConnection connection = (HttpURLConnection)url.openConnection();​
 +</​code>​
 +
 +The actual contents can be retrieved by using the InputStream provided by the HttpURLConnection instance:
 +<​code>​
 +InputStream is = connection.getInputStream();​
 +ByteArrayOutputStream result = new ByteArrayOutputStream();​
 +byte[] buffer = new byte[1024];
 +int length;
 +while ((length = is.read(buffer)) != -1) {
 +    result.write(buffer,​ 0, length);
 +}
 +return result.toString("​UTF-8"​);​
 +</​code>​
 +
 +The String contents returned from the doInBackground() method are passed as the parameter to the onPostExecute() method, which will be executed on the main UI thread. As such, within this method we can safely update the contents of the TextView:
 +<​code>​
 +@Override
 +protected void onPostExecute(String content) {
 +  textView.setText(content);​
 +}
 +</​code>​
 +
 +Having defined the AsyncTask launch it using the execute method, which receives the text from the EditText element as a parameter:
 +<​code>​
 +task.execute(editText.getText().toString());​
 +</​code>​
 +
 +If you try to run the application at this time, it will stop since, by default, applications are not granted Internet access by the framework. In order to be able to connect to remote resources, you need to add the necessary permission to the AndroidManifest.xml:​
 +<​code>​
 +<​uses-permission android:​name="​android.permission.INTERNET"​ />
 +</​code>​
 +
 +<note warning>
 +For each of the following tasks, do not forget to add the required permissions in the manifest file in order to be able to retrieve information about the location, WiFi). For mobile phones that have an OS version greater than Android 6.0, permissions for accessing or using location, Bluetooth need to be requested at runtime.
 +</​note>​
 +
 +=== Task 2 - Getting the last known location from providers (6p) ===
 +
 +For this task, you will need to have enabled either the GPS of your mobile device or an active WiFi connection (or both).
 +
 +On Android, the location can be gathered using three providers:
 +  * GPS provider which uses the GPS driver of your mobile device to track your connection (it is slow and very precise in the outdoors)
 +  * network provider which relies on having an active Internet connection (it has high levels of accuracy)
 +  * passive provider which relies on having the current location determined by other applications
 +
 +
 +Add a button and three TextViews to the activity. When clicking on the button, the TextViews will have to show the last known location coordinates (latitude and longitude) obtained from the GPS provider, the network provider and the passive provider. If a provider is disabled, display a significant message such as **WiFi/​GPS/​Passive provider is disabled**;
 +
 +In order to get the last known location, it is required to obtain a reference to a [[https://​developer.android.com/​reference/​android/​location/​LocationManager.html|LocationManager]] object. Through this object, you can send a request to the location providers in order to obtain the last known location. Make sure to obtain a last known location from all providers.
 +
 +<note warning>
 +In order to obtain location information,​ permissions for **ACCESS_FINE_LOCATION** and **ACCESS_COURSE_LOCATION** must be asked for and added to the manifest file. For devices that run an Android OS version greater or equal to 6, these permissions must be requested at runtime. Make sure to request these permissions only when they are not granted.
 +</​note>​
 +
 +To check if a permission is granted, the following code is used
 +<​code>​
 +if (ContextCompat.checkSelfPermission(context,​ permission) != PackageManager.PERMISSION_GRANTED) {
 +    // do something with ungranted permission
 +}
 +</​code>​
 +
 +The Activity class has a special method to request permissions at runtime using the following code
 +<​code>​
 +String[] PERMISSIONS = {Manifest.permission.ACCESS_FINE_LOCATION,​ Manifest.permission.ACCESS_COARSE_LOCATION};​
 +int PERMISSIONS_REQUEST_CODE = 123;
 +
 +requestPermissions(PERMISSIONS,​ PERMISSIONS_REQUEST_CODE);​
 +</​code>​
 +
 +By using this method, you tell the activity that it should request the list of permissions given in the array and use the request code offered. The activity must also override the **onRequestPermissionsResult** method which is called after user input regarding permissions. Remember to call the super method for this.
 +
 +<​code>​
 +@Override
 +public void onRequestPermissionsResult(int requestCode,​ @NonNull String[] permissions,​ @NonNull int[] grantResults) {
 +    super.onRequestPermissionsResult(requestCode,​ permissions,​ grantResults);​
 +    // your code here  ​
 +    if (requestCode == PERMISSIONS_REQUEST_CODE) {
 +        if (grantResults.length >= PERMISSIONS.length) {
 +            for (int i = 0; i < grantResults.length;​ i++) {
 +                if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
 +                    Log.d(TAG, "​onRequestPermissionsResult:​ Permission " + permissions[i] +
 +                        " was not granted"​);​
 +                }
 +            }
 +        } else {
 +              Log.d(TAG, "​onRequestPermissionsResult:​ not all permissions were granted"​);​
 +        }
 +    }  ​
 +}
 +</​code>​
 +
 +The grantResult array is related to the permissions array and contains information about the user's input to the request.
 +
 +To request permissions at runtime that were not granted, the following steps must be made:
 +
 +  - Check if permissions are granted
 +  - Save all the ungranted permissions
 +  - Request ungranted permissions
 +  - Check result of permissions request
 +
 +<note warning>
 +When trying to obtain the last known location, if permissions are not granted, a [[https://​developer.android.com/​reference/​java/​lang/​SecurityException.html|SecurityException]] exception is thrown. Make sure to catch this exception in a try-catch block
 +<​code>​
 +try {
 +    ...
 +} catch (SecurityException e) {
 +    // log exception thrown
 +}
 +</​code>​
 +</​note>​
 +
 +<note warning>
 +Take into account the fact that the location updates take time. Therefore the TextView will not be changed immediately,​ it will be changed after 10-12 seconds if a network provider was used to determine the location or more than 20 seconds if a GPS provider is used.
 +</​note>​
 +
 +=== Task 3 - Getting current and changes to location through service (7p) ===
 +
 +The LocationManager object also offers a method through which you can request location updates for a specific provider. This method receives a minimum time when updates should be made and a minimum distance for when updates should be made. The [[https://​developer.android.com/​reference/​android/​location/​LocationListener.html|LocationListener]] interface is used to monitor location changes, as well as location provider changes (enabled/​disabled). In this task you will need to create a service that listens to location changes of a certain distance periodically,​ as well as location provider changes. Each time any change has occurred, an event will be sent to the MainActivity. Display each event in different TextViews. ​
 +
 +In order to send events to the MainActivity we will use a third-party library called [[https://​github.com/​greenrobot/​EventBus|EventBus]]. This will simplify communication between components of the application.
 +
 +As a first step import the library into the project by adding the following line in the application'​s build.gradle file, in the dependencies section.
 +<​code>​
 +compile '​org.greenrobot:​eventbus:​3.1.1'​
 +</​code> ​
 +
 +In order for a component of the application to listen for specific events, the component must register itself. In our case, we will register the MainActivity to listen for events. To do so override the **onResume** and **onPause** methods of the activity as follows. This tells the MainActivity when to start and stop listening for events sent through EventBus.
 +<​code>​
 +@Override
 +protected void onResume() {
 +    super.onResume();​
 +    EventBus.getDefault().register(this);​
 +}
 +
 +@Override
 +protected void onPause() {
 +    super.onPause();​
 +    EventBus.getDefault().unregister(this);​
 +}
 +</​code> ​
 +
 +It is recommended to create a base event class and each event class sent through the application should extend this class. For example an event base class could look like this 
 +<​code>​
 +public abstract class Event {
 +    private String message;
 +
 +    public Event(String message) {
 +        this.message = message;
 +    }
 +}
 +</​code>​
 +
 +And a location event class could look like this
 +<​code>​
 +public class LocationEvent extends Event {
 +    private Location location;
 +
 +    public LocationEvent(String message, Location location) {
 +        super(message);​
 +        this.location = location;
 +    }
 +
 +    public Location getLocation() {
 +        return location;
 +    }
 +}
 +</​code>​
 +
 +In order for the main activity to listen to events, it must declare a method for each type of event it listens to. The name of the method is always **onEventMainThread** and the parameters of the method should be of event type. The method must always be public. In our case, for location events our method should look like
 +<​code>​
 +@Subscribe
 +public void onEventMainThread(LocationEvent event) {
 +    // your code here
 +}
 +</​code>​
 +
 +To send an event through the application the following method is used where **location** is the information about the location contained in the event.
 +<​code>​
 +EventBus.getDefault().post(new LocationEvent("​Location changed event",​ location));
 +</​code>​
 +
 +<note warning>​Make sure that you have at least one **onEventMainThread** method in your component that registers to EventBus. If you have no method with this name an exception will be thrown and your application will crash.</​note>​
 +
 +To create a service, define a class that extends the [[https://​developer.android.com/​reference/​android/​app/​Service.html|Service]] class and implements the [[https://​developer.android.com/​reference/​android/​location/​LocationListener.html|LocationListener]] class. This will require for you to override the onBind method
 +
 +<​code>​
 +@Nullable
 +@Override
 +public IBinder onBind(Intent intent) {
 +    return null;
 + }
 +</​code>​
 +
 +To start the service you have to create an [[https://​developer.android.com/​reference/​android/​content/​Intent.html|Intent]] object for the specific service and call the **startService** method from the Activity/​Context of the application. Create a method **startService** in your LocationService.
 +<​code>​
 +public static void startService(Context context) {
 +    Log.i(TAG, "​startService:​ Location service started"​);​
 +    Intent serviceIntent = new Intent(context,​ LocationService.class);​
 +    context.startService(serviceIntent);​
 +}
 +</​code>​
 +
 +This method must be called when the application starts.
 +
 +By overriding the **onStartCommand** in the service, you define how the service behaves. Override this method and initialize the LocationManager object, request the service to perform periodical updates and get the last known location for WiFi and GPS providers. ​
 +
 +<​code>​
 +@Override
 +public int onStartCommand(Intent intent, int flags, int startId) {
 +    // your initialization code there
 +    // get last known location for providers
 +
 +    return START_STICKY;​
 +}
 +</​code>​
 +
 +Do not forget to add the service to the **AndroidManifest.xml** file in the **application** tag.
 +
 +<​code>​
 +<service
 +    android:​name="​.services.LocationService"​
 +    android:​enabled="​true"></​service>​
 +</​code>​
 +
 +Given the information provided above, use EventBus to send events regarding location changes, provider changes (enabling and disabling) as well as provider status changes from the Location Service designed which listens to changes. Build the service so that when it starts it gets the current location from both GPS and WiFi providers and sends an Event for each to the MainActivity.
 +
 +=== Task 4 - Scaning for WiFi devices (3p) ===
 +
 +In this task you will scan for WiFi devices on the click of a button. ​
 +
 +Before going any further add the following classes and resource files to your project.
 +
 +Add the **NonScrollListView** class
 +<​code>​
 +public class NonScrollListView extends ListView {
 +
 +    public NonScrollListView(Context context) {
 +        super(context);​
 +    }
 +
 +    public NonScrollListView(Context context, AttributeSet attrs) {
 +        super(context,​ attrs);
 +    }
 +
 +    public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
 +        super(context,​ attrs, defStyle);
 +    }
 +
 +    @Override
 +    public void onMeasure(int widthMeasureSpec,​ int heightMeasureSpec) {
 +        int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
 +                Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);​
 +        super.onMeasure(widthMeasureSpec,​ heightMeasureSpec_custom);​
 +        ViewGroup.LayoutParams params = getLayoutParams();​
 +        params.height = getMeasuredHeight();​
 +    }
 +}
 +</​code>​
 +
 +Add the **ListViewAdapter** class to your project
 +
 +<​code>​
 +public class ListViewAdapter extends BaseAdapter {
 +
 +    private List<​String>​ elements;
 +    private LayoutInflater layoutInflater;​
 +
 +    public ListViewAdapter(List<​String>​ elements, Context context) {
 +        this.elements = elements;
 +        this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);​
 +    }
 +
 +    @Override
 +    public int getCount() {
 +        return elements.size();​
 +    }
 +
 +    @Override
 +    public Object getItem(int i) {
 +        return elements.get(i);​
 +    }
 +
 +    @Override
 +    public long getItemId(int i) {
 +        return i;
 +    }
 +
 +    @Override
 +    public View getView(int i, View view, ViewGroup viewGroup) {
 +        view = layoutInflater.inflate(R.layout.element_layout,​ null);
 +
 +        TextView element = view.findViewById(R.id.element);​
 +        element.setText(elements.get(i));​
 +
 +        return view;
 +    }
 +}
 +</​code>​
 +
 +Add the **element_layout.xml** file to your layout folder
 +
 +<​code>​
 +<?xml version="​1.0"​ encoding="​utf-8"?>​
 +<​LinearLayout xmlns:​android="​http://​schemas.android.com/​apk/​res/​android"​
 +              android:​layout_width="​match_parent"​
 +              android:​layout_height="​match_parent"​
 +              android:​orientation="​vertical">​
 +
 +    <​TextView
 +        android:​id="​@+id/​element"​
 +        android:​layout_width="​match_parent"​
 +        android:​layout_height="​wrap_content"​
 +        android:​layout_margin="​8dp"/>​
 +</​LinearLayout>​
 +</​code>​
 +
 +In your activity layout (activity_main.xml) add the following elements
 +
 +<​code>​
 +<Button
 +    android:​id="​@+id/​button4"​
 +    android:​layout_width="​match_parent"​
 +    android:​layout_height="​wrap_content"​
 +    android:​layout_margin="​8dp"​
 +    android:​text="​Task 4"/>​
 +
 +<!-- This should be the NonScrollListView class in your project (package may vary, insert the correct package) -->
 +<​com.cs.pub.smd.laborator03.views.NonScrollListView
 +    android:​id="​@+id/​task4"​
 +    android:​layout_width="​match_parent"​
 +    android:​layout_height="​wrap_content"​
 +    android:​layout_margin="​8dp"/>​
 +</​code>​
 +
 +Get a reference to the **Button** and the **NonScrollListView** elements in the onCreate method for the Activity.
 +
 +Add an **OnClickListener** to the new button
 +
 +<​code>​
 +button4.setOnClickListener(new View.OnClickListener() {
 +            @Override
 +            public void onClick(View view) {
 +                List<​String>​ elements = new ArrayList<>​();​
 +
 +                // your code here
 +
 +                ListViewAdapter adapter = new ListViewAdapter(elements,​ getApplicationContext());​
 +                task4.setAdapter(adapter);​
 +            }
 +        });
 +</​code>​
 +
 +Use the [[https://​developer.android.com/​reference/​android/​net/​wifi/​WifiManager.html|WiFiManager]] to scan for WiFi devices and print their MAC address. Do this in the **OnClickListener** for the button and add the MAC addresses to the **elements** list.
  
smd/laboratoare/old/03.txt ยท Last modified: 2019/03/06 14:52 by adriana.draghici
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0