Monthly Archives: Abril 2015

Testa Rede

Esta aplicação testa se a rede está disponível.
Caso não haja uma ligação de rede, a aplicação lança uma caixa de diálogo a informar o utilizador e pergunta-lhe se quer ativar a rede. Se o utilizador anuir, é lançada uma janela do sistema para ativação da rede. Caso contrário a aplicação termina.
Quando o utilizador desliga a janela de sistema, a aplicação volta a avaliar se tem rede ativa.

Para lançar uma caixa de diálogo, é utilizada a classe AlertDialog.Builder. Existem muitas possibilidades gráficas de utilização da classe AlertDialog.Builder.

A função ativaRede2 lança a janela de configuração do sistema para ativação de uma ligação de rede. Para isso utiliza as instruções seguintes:

        Intent i = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
        startActivityForResult(i,codigo);

Quando a janela de sistema de configuração da rede termina, a aplicação retoma a execução na função onActivityResult. Aqui, devemos avaliar o valor do parâmetro requestCode para saber de que Activity acabámos de retornar. O parâmetro requestCode contém o valor da variável codigo que enviámos para a função startActivityForResult.

Código do layout gráfico:

 <RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".TestaRede">

    <TextView
        android:textSize="36sp"
        android:text="@string/hello_world"
        android:id="@+id/texto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</RelativeLayout>

Código da classe principal:

 package com.se.testarede;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.provider.Settings;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;


public class TestaRede extends ActionBarActivity {

    final int REQUEST_WIRELESS = 1;
    final int REQUEST_WIRELESS_2 = 2;

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

        acao_principal();
    }

    void acao_principal() {
        boolean haRede = isNetworkAvailable();
        if(!haRede) {
            ativaRede(REQUEST_WIRELESS);
        } else {
            TextView texto = (TextView)findViewById(R.id.texto);
            texto.setText("A rede já está ativa");
        }
    }

    public boolean isNetworkAvailable() {
        ConnectivityManager connectivityManager = (ConnectivityManager)
                getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null;
    }

    public void ativaRede(final int codigo) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage(getResources().getString(R.string.sync_ativar_rede))
                .setCancelable(false)
                .setPositiveButton(getResources().getString(R.string.yes), new DialogInterface.OnClickListener(){
                    public void onClick(DialogInterface dialog, int id) {
                        ativaRede2(codigo);
                    }
                })
                .setNegativeButton(getResources().getString(R.string.no), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                        finish();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    public void ativaRede2(int codigo) {
        Intent i = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
        startActivityForResult(i,codigo);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        switch(requestCode) {
            case REQUEST_WIRELESS:
                Log.d("LOG", "onActivityResult().REQUEST_WIRELESS");
                acao_principal();
                break;
            case REQUEST_WIRELESS_2:
                // outra operação
                break;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_testa_rede, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Código do Android Manifest:

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

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Ficheiro strings.xml:

 <resources>
    <string name="app_name">TestaRede</string>
    <string name="hello_world">Olá Mundo!</string>
    <string name="action_settings">Configuração</string>
    <string name="sync_ativar_rede">Esta aplicação precisa de rede para funcionar. Quer ativar a rede agora?</string>
    <string name="yes">Sim</string>
    <string name="no">Não</string>
</resources>

Login, HTTP e AsyncTask

Neste artigo, apresenta-se uma comunicação http simples para fazer login num servidor Web. A comunicação é feita dentro de uma thread própria, utilizando a classe AsyncTask do Android.

A comunicação HTTP no Android deve ser feita dentro de uma thread para evitar que a aplicação fique “presa” à espera da resposta do servidor, não permitindo, durante esse tempo, que o utilizador interaja com a mesma.

Para além das classes HttpRequest e HttpData (a primeira implementa os métodos de invocação dos comandos GET e POST; a segunda gere a resposta do servidor que pode ser um bloco de texto ou um ficheiro binário) que foram utilizadas no último projeto, Utiliza-se também a classe PostData que permite fazer pedidos http diferentes, utilizando a mesma thread, e tratar as respetivas respostas.

Segue-se o código do ficheiro activity_login.xml, com o formulário de login:

<LinearLayout 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=".Login"
    android:orientation="vertical"
    >

    <TextView
        android:text="Login"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="48sp"
        />
    <TextView
        android:text="Username"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:paddingTop="20sp"
        />
    <EditText
        android:id="@+id/user"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        />
    <TextView
        android:text="Password"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:paddingTop="20sp"
        />
    <EditText
        android:id="@+id/pass"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:inputType="textPassword"
        />
    <Button
        android:id="@+id/login"
        android:text="Login"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:layout_marginTop="20sp"
        />
</LinearLayout>

 

Segue-se o código da classe HttpRequest, com os métodos de comunicação HTTP:

package com.se.login;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

public class HttpRequest {
    public HttpClient httpclient;

    public HttpRequest() {
        HttpParams httpParameters = new BasicHttpParams();
        // Set the timeout in milliseconds until a connection is established.
        // The default value is zero, that means the timeout is not used.
        int timeoutConnection = 30000;
        HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
        // Set the default socket timeout (SO_TIMEOUT)
        // in milliseconds which is the timeout for waiting for data.
        int timeoutSocket = 60000;
        HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

        httpclient = new DefaultHttpClient(httpParameters);
        httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
    }

    // Faz um pedido GET
    public HttpData get(String url){
        HttpData dados = new HttpData();
        HttpGet request = new HttpGet();
        try {
            request.setURI(new URI(url));
            HttpResponse resp = httpclient.execute(request);
            dados.set(resp);
        } catch (URISyntaxException e1) {
            e1.printStackTrace();
        } catch (ClientProtocolException e1) {
            e1.printStackTrace();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        return dados;
    }
    public HttpData getImage(String url, List<NameValuePair> vars) {
        HttpData dados = new HttpData();
        HttpPost request = new HttpPost(url);
        try {
            request.setEntity(new UrlEncodedFormEntity(vars));
            HttpResponse resp = httpclient.execute(request);
            dados.setImage(resp);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dados;
    }

    // Faz um pedido via POST, com variáveis
    public HttpData post(String url, List<NameValuePair> vars){
        HttpData dados = new HttpData();
        HttpPost request = new HttpPost(url);
        try {
            request.setEntity(new UrlEncodedFormEntity(vars));
            HttpResponse resp = httpclient.execute(request);
            dados.set(resp);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dados;
    }

    // Faz um upload de dados binários via POST
    public HttpData post(String url, String name, String filename, byte[] bin, String type){
        HttpData dados = new HttpData();
        HttpPost request = new HttpPost(url);
        SimpleMultipartEntity mpe = new SimpleMultipartEntity();
        mpe.addPart(name, filename, bin, type);
        request.setEntity(mpe);
        HttpResponse resp = null;
        try {
            resp = httpclient.execute(request);
        } catch (ClientProtocolException e1) {
            e1.printStackTrace();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        dados.set(resp);

        return dados;
    }
}

 

Segue-se o código da classe HttpData, onde ficam os resultados das consultas HTTP:

package com.se.login;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Hashtable;

import org.apache.http.Header;
import org.apache.http.HttpResponse;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

public class HttpData {
    public String content;
    public Hashtable<String,String> cookies;
    public Hashtable<String,String> headers;
    public int statusCode;
    public Bitmap imagem;

    public HttpData() {
        content = "";
        imagem = null;
        cookies = new Hashtable<String,String>();
        headers = new Hashtable<String,String>();
        statusCode = 0;
    }

    public void set(HttpResponse resp) {
        if(resp == null) return;
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()));
            StringBuffer sb = new StringBuffer("");
            String line = "";
            String NL = System.getProperty("line.separator");
            while ((line = in.readLine()) != null) {
                if(sb.length() != 0) sb.append(NL);
                sb.append(line);
            }
            in.close();
            content = sb.toString();
            if(content.startsWith("")) content = content.substring(3);
            Log.d("LOG", "HttpData.set(), tamanho de content: " + content.length());
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        Header[] hds = resp.getAllHeaders();
        for(Header h: hds) {
            headers.put(h.getName(), h.getValue());
            if (h.getName().equals("set-cookie"))
                cookies.put(h.getName(), h.getValue());

        }
        statusCode = resp.getStatusLine().getStatusCode();
        Log.d("NET", "statusCode = " + statusCode);
    }

    public void setImage(HttpResponse resp) {
        if(resp == null) return;
        InputStream in = null;
        try {
            in = resp.getEntity().getContent();
            imagem = BitmapFactory.decodeStream(in);
            Log.d("LOG", "HttpData.setImage(), tamanho de content: " + content.length());
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        Header[] hds = resp.getAllHeaders();
        for(Header h: hds) {
            headers.put(h.getName(), h.getValue());
            if (h.getName().equals("set-cookie"))
                cookies.put(h.getName(), h.getValue());

        }
        statusCode = resp.getStatusLine().getStatusCode();
        Log.d("NET", "statusCode = " + statusCode);
    }
}

 

Segue-se o código da classe SimpleMultipartEntity, para poder submeter ficheiros binários:

package com.se.login;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;

import android.util.Log;

public class SimpleMultipartEntity implements HttpEntity {

    // Original: http://blog.rafaelsanches.com/2011/01/29/upload-using-multipart-post-using-httpclient-in-android/
    // Modified by: Alex Banha

    private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            .toCharArray();

    private String boundary = null;

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    boolean isSetLast = false;
    boolean isSetFirst = false;

    public SimpleMultipartEntity() {
        final StringBuffer buf = new StringBuffer();
        final Random rand = new Random();
        for (int i = 0; i < 30; i++) {
            buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
        }
        this.boundary = buf.toString();

    }

    public void writeFirstBoundaryIfNeeds(){
        if(!isSetFirst){
            try {
                out.write(("--" + boundary + "\r\n").getBytes());
            } catch (final IOException e) {
                Log.e("LOG", e.getMessage(), e);
            }
        }
        isSetFirst = true;
    }

    public void writeLastBoundaryIfNeeds() {
        if(isSetLast){
            return ;
        }
        try {
            out.write(("\r\n--" + boundary + "--\r\n").getBytes());
        } catch (final IOException e) {
            Log.e("LOG", e.getMessage(), e);
        }
        isSetLast = true;
    }

    public void addPart(final String key, final String value) {
        writeFirstBoundaryIfNeeds();
        try {
            out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n").getBytes());
            out.write("Content-Type: text/plain; charset=UTF-8\r\n".getBytes());
            out.write("Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes());
            out.write(value.getBytes());
            out.write(("\r\n--" + boundary + "\r\n").getBytes());
        } catch (final IOException e) {
            Log.e("LOG", e.getMessage(), e);
        }
    }

    public void addPart(final String key, final String fileName, final InputStream fin){
        addPart(key, fileName, fin, "application/octet-stream");
    }

    public void addPart(final String key, final String fileName, final InputStream fin, String type){
        writeFirstBoundaryIfNeeds();
        try {
            type = "Content-Type: "+type+"\r\n";
            out.write(("Content-Disposition: form-data; name=\""+ key+"\"; filename=\"" + fileName + "\"\r\n").getBytes());
            out.write(type.getBytes());
            out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());

            final byte[] tmp = new byte[4096];
            int l = 0;
            while ((l = fin.read(tmp)) != -1) {
                out.write(tmp, 0, l);
            }
            out.flush();
        } catch (final IOException e) {
            Log.e("LOG", e.getMessage(), e);
        } finally {
            try {
                fin.close();
            } catch (final IOException e) {
                Log.e("LOG", e.getMessage(), e);
            }
        }
    }

    public void addPart(final String key, final String fileName, byte[] dados, String type){
        writeFirstBoundaryIfNeeds();
        try {
            type = "Content-Type: "+type+"\r\n";
            out.write(("Content-Disposition: form-data; name=\""+ key+"\"; filename=\"" + fileName + "\"\r\n").getBytes());
            out.write(type.getBytes());
            out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());
            out.write(dados);
            out.flush();
        } catch (final IOException e) {
            Log.e("LOG", e.getMessage(), e);
        }
    }

    public void addPart(final String key, final File value) {
        try {
            addPart(key, value.getName(), new FileInputStream(value));
        } catch (final FileNotFoundException e) {
            Log.e("LOG", e.getMessage(), e);
        }
    }

    public long getContentLength() {
        writeLastBoundaryIfNeeds();
        return out.toByteArray().length;
    }

    public Header getContentType() {
        return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
    }

    public boolean isChunked() {
        return false;
    }

    public boolean isRepeatable() {
        return false;
    }

    public boolean isStreaming() {
        return false;
    }

    public void writeTo(final OutputStream outstream) throws IOException {
        outstream.write(out.toByteArray());
    }

    public Header getContentEncoding() {
        return null;
    }

    public void consumeContent() throws IOException,
            UnsupportedOperationException {
        if (isStreaming()) {
            throw new UnsupportedOperationException(
                    "Streaming entity does not implement #consumeContent()");
        }
    }

    public InputStream getContent() throws IOException,
            UnsupportedOperationException {
        return new ByteArrayInputStream(out.toByteArray());
    }
}

 

Segue-se o código da classe PostData, com os dados dos pedidos HTTP (URL, variáveis e binários):

package com.se.login;

import java.util.List;

import org.apache.http.NameValuePair;

public class PostData {
   public String url;
   public List<NameValuePair> dados;
   byte[] binario;

 public PostData(String url, List<NameValuePair> dados, byte[] binario){
    this.url = url;
    this.dados = dados;
    this.binario = binario;
  }
}

 

Segue-se o código do ficheiro AndroidManifest.xml, com as permissões para acesso à Internet:

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

    <uses-permission android:name="android.permission.INTERNET" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

Segue-se o código da classe principal, a clase Login.java:

package com.se.login;

import android.os.AsyncTask;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import java.util.ArrayList;
import java.util.List;


public class Login extends Activity {

    EditText user, pass;
    Button login;

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

        user = (EditText) findViewById(R.id.user);
        pass = (EditText) findViewById(R.id.pass);
        login = (Button) findViewById(R.id.login);

        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                fazLogin();
            }
        });
    }

    public void fazLogin() {
        String sUser = user.getText().toString();
        String sPass = pass.getText().toString();

        String url = "http://escola.w3.pt/se/_files/login.php";
        List<NameValuePair> dados = new ArrayList<NameValuePair>();
        dados.add(new BasicNameValuePair("usr", sUser));
        dados.add(new BasicNameValuePair("psw", sPass));

        new Sincro().execute(new PostData(url, dados, null));
    }

    public class Sincro extends AsyncTask<PostData, Integer, HttpData> {
        @Override
        protected HttpData doInBackground(PostData... params) {
            Log.i("LOGGER", "Sincro Starting...");
            HttpData dados = null;
            try {
                HttpRequest req = new HttpRequest();
                dados = req.post(params[0].url, params[0].dados);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return dados;
        }

        protected void onProgressUpdate(Integer... progress) {
        }

        @Override
        protected void onPostExecute(HttpData dados) {
            Log.i("LOGGER", "...Done");
            Toast.makeText(Login.this, dados.content, Toast.LENGTH_LONG).show();
        }
    }
}

 

Segue-se o código do ficheiro login.php que está no servidor Web:

<?php

if($_POST['usr']=='se' && $_POST['psw']=='xpto') echo 'OK';
else echo 'NOT OK';

?>

Nota: a partir da versão 23 do sdk, o Android deixou de suportar o HttpClient. A partir desta versão, há as opções seguintes, para comunicar pela rede:

  • usar a classe URLConnection
  • compilar para a versão 22 do sdk (compile ‘com.android.support:appcompat-v7:22.2.0’)
  • ou, no AndroidStudio, com a versão 23, colocar no gradle: android { useLibrary ‘org.apache.http.legacy’ }
  • ou, no Eclipse, com a versão 23, adicionar a biblioteca org.apache.http.legacy.jar, que está em Android/Sdk/platforms/android-23/optional, às dependências do projeto
  • importar a biblioteca HttpClient jar para o projeto

(ver mais pormenores aqui e aqui)