sexta-feira, 19 de outubro de 2012

Repositórios Debian

Alguns repositórios Debian

Aqui fica uma lista de alguns repositórios Debian bem jeitosos. O sources.list fica apenas com os repositórios oficiais Debian. Os outros repositórios ficam cada um no seu ficheiro .list na diretoria sources.list.d, para ser mais fácil gerir.

 

sources.list


deb http://ftp.pt.debian.org/debian/ squeeze main contrib non-free

deb-src http://ftp.pt.debian.org/debian/ squeeze main contrib non-free

deb http://security.debian.org/ squeeze/updates main contrib non-free
deb-src http://security.debian.org/ squeeze/updates main contrib non-free

# squeeze-updates, previously known as 'volatile'
deb http://ftp.pt.debian.org/debian/ squeeze-updates main contrib non-free
deb-src http://ftp.pt.debian.org/debian/ squeeze-updates main contrib non-free

sources.list.d/dropbox.list


# dropbox
# apt-key adv --keyserver pgp.mit.edu --recv-keys 5044912E
deb http://linux.dropbox.com/debian squeeze main

sources.list.d/google.list


##########################
# Google APT Repositories #
##########################
# wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
# Google Chrome repo http://www.google.com/linuxrepositories/
deb http://dl.google.com/linux/chrome/deb/ stable main
# Google Talk browser plugin http://www.google.com/chat/video
# deb http://dl.google.com/linux/talkplugin/deb/ stable main
# Google Earth
# deb http://dl.google.com/linux/earth/deb/ stable main
# Google's Music Manager
# http://www.google.com/support/music/bin/answer.py?answer=1229970
# deb http://dl.google.com/linux/musicmanager/deb/ stable main

sources.list.d/mozilla.list


# mozilla
# http://mozilla.debian.net
# iceweasel, icedove, iceape
# apt-get install pkg-mozilla-archive-keyring
# deb http://mozilla.debian.net/ squeeze main
# firefox, thunderbird
# apt-key adv --recv-keys --keyserver keyserver.ubuntu.com C1289A29
deb http://downloads.sourceforge.net/project/ubuntuzilla/mozilla/apt all main
# os pacotes são firefox-mozilla-build, thunderbird-mozilla-build, seamonkey-mozilla-build

sources.list.d/multimedia.list


# deb multimedia
# http://deb-multimedia.org
# apt-get update
# apt-get install deb-multimedia-keyring
deb http://deb-multimedia.org squeeze main non-free
# deb http://deb-multimedia.org squeeze-backports main

sources.list.d/playonlinux.list


# play-on-linux
# wget -q "http://deb.playonlinux.com/public.gpg" -O- | apt-key add -
deb http://deb.playonlinux.com/ squeeze main

sources.list.d/virtualbox.list


# virtualbox
# wget -q http://download.virtualbox.org/virtualbox/debian/oracle_vbox.asc -O- | sudo apt-key add -
deb http://download.virtualbox.org/virtualbox/debian squeeze contrib non-free

sources.list.d/scribus.list


# scribus
# apt-key adv --recv-keys --keyserver subkeys.pgp.net EEF818CF
deb http://debian.scribus.net/debian stable main
deb-src http://debian.scribus.net/debian stable main

Referências

https://sites.google.com/site/mydebiansourceslist/

quinta-feira, 23 de agosto de 2012

logwatch, fail2ban, molly-guard

Mais umas excelentes ferramentas para sistemas linux

logwatch


Instala-se com o habitual

apt-get install logwatch

E já está.
A instalação cria um script em /etc/cron.daily que se encarrega de correr o logwatch e enviar um email para o root.

Para modificar as opções pré-definidas é preciso copiar o ficheiro de configuração de exemplo para /etc/logwatch/conf:


cp /usr/share/logwatch/default.conf/logwatch.conf /etc/logwatch/conf/

E editar o ficheiro. No Debian a opção TmpDir estava mal configurada, deve ser mudada para /tmp.
Todas as opções podem ser sobrepostas através de parâmetros na linha de comandos.

O logwatch apenas cria o relatório com os eventos mais significativos do sistema, é responsabilidade do administrador ler o relatório e agir em conformidade.

fail2ban

apt-get install fail2ban

E já está.
A configuração básica está em /etc/fail2ban/fail2ban.conf e a configuração mais específica em /etc/fail2ban/jail.conf.

Apenas a proteção das ligações SSH está pré-definida, para outros protocolos/aplicações é preciso editar o /etc/fail2ban/jail.conf.

molly-guard

apt-get install molly-guard

E já está.
O molly-guard pede o nome da máquina quando tentamos desligar uma máquina através de SSH.

Algumas coisas básicas a fazer numa máquina nova

Um pequeno conjunto de aplicações e configurações a executar em máquinas novas (linux claro, Debian de preferência)

Rede


Primeiro que tudo é sempre preciso configurar corretamente a rede.
Se a interface de rede é configurada por DHCP não há muito a fazer, mas se queremos um IP estático é preciso editar o /etc/network/interfaces e substituir o dhcp por static na interface pretendida e indicar, pelo menos o endereço, netmask e gateway, qualquer coisa assim:


auto eth0
iface eth0 inet static
        address 192.168.1.31
        netmask 255.255.255.0
        gateway 192.168.1.254

Mas nem só de IP vive a rede. Também é importante definir os servidores de DNS, o hostname e o domain name. Mais uma vez o DHCP trata disto, mas se quisermos um IP estático temos de editar /etc/resolv.conf, /etc/hostname e /etc/hosts.

É preciso escolher um hostname, que identifica a máquina e um nome de domínio, que identifica a rede local. Vamos usar server001 e lan, respetivamente.

Em /etc/resolv.conf colocamos o nome de domínio e os servidores de DNS. DNS é um serviço crítico, por isso normalmente são definidos pelo menos dois. O ficheiro pode ficar com este conteúdo:

domain lan
search lan
nameserver 208.67.220.220
nameserver 208.67.222.222

Neste caso o domínio chama-se lan (é o pré-definido) e dizemos que queremos pesquisar o domínio lan. As duas entradas nameserver são os dois servidores DNS a usar. Os IPs indicados são do serviço OpenDNS e podem ser usados livremente. Por vezes há uma única entrada nameserver que corresponde ao gateway da rede, é usual em sistemas configurados por DHCP.

Em /etc/hostname colocamos o hostname (basicamente o nome da máquina), assim:

server001

No ficheiro hosts convém colocar uma entrada para o hostname definido antes, qualquer coisa como:

127.0.0.1 localhost
127.0.0.1 server001 server001.lan

Para que tudo funcione é preciso reiniciar os serviços respetivos:

/etc/init.d/networking restart
/etc/init.d/hostname start

Tempo


Parecendo que não, é bastante importante que o relógio e o fuso horário do sistema estejam certos, principalmente se vamos usar coisas como servidores de mail, serviços de autenticação  e até para os logs do sistema.
Para configurar o fuso horário:

dpkg-reconfigure tzdata

Para acertar a hora o melhor é deixar que o sistema se atualize automaticamente, é para isso que existe o serviço NTP. Basta fazer:

apt-get install ntp

E esperar um pouco enquanto o sistema sincroniza.
Podemos ver o estado do NTP com:

ntpq -p

Para ver a data e hora atual fazer:

date

Quando a hora estiver certa convém atualizar o relógio de hardware, para poder manter a hora certa (pelo menos com pequenas variações) mesmo enquanto o sistema está desligado. Para atualizar o relógio de hardware fazemos:

hwclock --systohc

Locale

Chama-se locale ao conjunto de definições que informam o sistema a língua a utilizar e os formatos para apresentação de datas, horas, valores monetários, etc.
É importante definir corretamente o locale que pretendemos usar, pois se não o fizermos arriscamo-nos a receber mensagens numa língua que não entendemos.
Para escolher os locales a gerar fazemos:

dpkg-reconfigure locales

Os locales selecionados serão criados. Pode demorar alguns segundos (ou minutos) a gerar cada locale.
O comando locale mostra as variáveis definidas.
Se alguma estiver em branco pode ser necessário defini-la manualmente com, por exemplo:

export LC_TIME=en_US.UTF-8

Ainda não consegui descobrir porque é que o dpkg-reconfigure locales nem sempre define todas as variáveis LC_* e LANG*.

Um script útil em servidores (principalmente os que têm pouco espaço em disco) é o localepurge. É só instalar com:

apt-get install localepurge

É preciso selecionar os locales a MANTER, todos os outros serão apagados.
Da próxima vez que se fizer uma instalação com o apt-get, ou se correr manualmente o localepurge, serão apagados os locales que não foram selecionados. Este script não é 'oficial' e pode causar alguns erros (inofensivos). Usar com precaução e RTFM.

rkhunter

Um caçador de rootkits para linux e BSD

Para instalar o rkhunter pode fazer-se o download a partir do site oficial e correr o script de instalação, mas como de costume prefiro os repositórios Debian:

apt-get install rkhunter

Ou para quem prefere otimizar o código (e tiver o apt-build instalado):

apt-build install rkhunter

Para correr manualmente, convém fazer uma atualização e só depois fazer a verificação:

rkhunter --update
rkhunter --check

Também podemos correr o comando:

rkhunter --propupd

Que cria uma base de dados com as propriedades dos ficheiros executáveis presentes no sistema. Das próximas vezes que o rkhunter for executado (SEM a opção --propupd) compara os ficheiros com a informação guardada e avisa se ocorreu alguma alteração. É claro que:

  1. É da responsabilidade do utilizador assegurar que todos os executáveis são legítimos quando se executa o --propupd;
  2. O rkhunter apenas avisa se ocorrer alguma alteração dos ficheiros, não tem forma de saber se essa alteração é fidedigna ou não (por exemplo, um apt-get upgrade altera as propriedades dos ficheiros).

O rkhunter costuma gerar alguns avisos 'falsos positivos', mas devem ser investigados, porque às vezes um verdadeiro positivo está escondido atrás de um falso.

A instalação do rkhunter cria os scripts /etc/cron.daily/rkhunter e /etc/cron.weekly/rkhunter que se encarregam de correr o rkhunter diariamente e de o atualizar semanalmente.
Os ficheiros /etc/default/rkhunter e /etc/rkhunter.conf têm as configurações a usar, mas as que estão pré-definidas são normalmente suficientes.

Referências

http://rkhunter.sourceforge.net/


segunda-feira, 30 de julho de 2012

Wake on lan e auto shutdown

Wake on lan e auto shutdown

Auto shutdown

O autoshutdown é simples, basta colocar o comando no crontab, mas esqueço-me sempre que os comandos no crontab precisam do caminho completo, porque a PATH é mais limitada.
Basta editar (como root) o crontab com:

crontab -e

E acrescentar uma linha do tipo:

00 03 * * * /sbin/shutdown -h +5

Isto vai fazer com que o computador se desligue às 3:05 da manhã, dando um aviso com 5 minutos de antecedência.

Wake on LAN

O Wake on LAN não é uma opção muito segura. Apesar de haver extensões ao protocolo original que acrescentam autenticação, o WOL original é muito inseguro. Mesmo assim pode dar jeito em casa :)
O computador deve ser configurado na BIOS para suportar o Wake on LAN. Nem todas as BIOS o suportam, nem todas as interfaces de rede o suportam e a forma como aparece na BIOS pode ser muito variada. Pode aparacer mesmo como "Wake on LAN" ou "Wake on PCI event" ou "Wake on PCI-E event" ou qualquer coisa parecida.
Mesmo assim pode ainda não ser suficiente para que o WOL funcione. O sistema operatvo também tem de colaborar. No Windows isso faz-se nas propriedades avançadas da interface de rede. No linux faz-se com o ethtool.
Para verificar se a interface de rede suporta Wak on LAN basta executar (como root):

ethtool eth0|grep Wake

Se não aparecer nada significa que a interface não suporta WOL.
Para assegurar que o linux habilita o WOL criei um script em /etc/network/if-ip.d/wolenable, com o seguinte conteúdo:

#!/bin/sh
ethtool -s eth0 wol g

Não esquecer de tornar o script executável com:

chmod +x /etc/network/if-up.d/wolenable

Para que o script seja executado é preciso adicionar uma linha à configuração da interface de rede em /etc/network/interfaces:

auto eth0
iface eth0 inet dhcp
    post-up /etc/network/if-up.d/wolenable

Isto vai executar o script cada vez que a interface eth0 seja iniciada (a linha nova está a negrito).
Depois é só uma questão de a partir de outra máquina (no mesmo domínio de broadcast) executar:

wakeonlan 01:23:45:67:89:ab

Substituindo o 01:23:45:67:89:ab pelo MAC address da máquina, claro.

sexta-feira, 27 de julho de 2012

Instalar o Debian por PXE

Instalar o Debian por PXE

A documentação do Debian, mas por vezes está um pouco desatualizada, como é o caso, por isso aqui fica um guião para instalar o Debian 6 por PXE.
Para instalar por PXE é preciso:


  1. Que o cliente esteja definido para arrancar por rede
  2. Um servidor de PXE com:
    1. Servidor DHCP corretamente configurado
    2. Servidor TFTP corretamente configurado
    3. Os ficheiro de arranque do Debian colocados no sítio certo.


1. Definir o arranque por rede

É preciso aceder à configuração da BIOS e definir o arranque por rede (ou PXE, ou LAN, ou ...). às vezes é mais complicado que que parece. Em algumas motherboards é preciso ativar a placa de rede e ou o arranque por PXE e depois reiniciar e voltar a entrar nas configurações da BIOS e só então aparece o arranque por LAN como opção de arranque. Caso o equipamento não suporte mesmo  arranque por LAN, ainda assim é possível criar uma disquete com o http://rom-o-matic.net/ (o guião fica para outro dia).


2. Servidor PXE

Para fazer o arranque por PXE é necessário configurar os serviços de DHCP e TFTP (há outras opções, usando BOOTP ou RARP, mas DHCP+TFTP é o mais simples e eficaz). Não é necessário que os dois serviços estejam na mesma máquina, nem que sejam linux, mas é mais fácil se estiverem. Aqui vou usar o próprio Debian 6 para fazer um servidor de PXE.

2.1. Instalar e configurar o DHCP

O pacote recomendado é o isc-dhcp-server:
apt-get install isc-dhcp-server
A configuração é feita no ficheiro /etc/dhcp/dhcpd.conf.
Coloquei o seguinte conteúdo:

ddns-update-style none;


option domain-name "lan";
option domain-name-servers 208.67.222.222, 208.67.220.220;
option subnet-mask 255.255.255.0;
default-lease-time 600;
max-lease-time 7200;
server-name "pxeserver";
allow booting;
allow bootp;
authoritative;



subnet 192.168.13.0 netmask 255.255.255.0 {
  range 192.168.13.20 192.168.13.100;
  option routers 192.168.13.254;
  option broadcast-address 192.168.13.255;
  option domain-name-servers 208.67.222.222;
}

subnet 192.168.1.0 netmask 255.255.255.0 {
}

group {
  host tftpclient {
    hardware ethernet 08:00:27:FA:3B:A2;
    filename "/pxelinux.0";
  }
}

2.2. Servidor TFTP corretamente configurado

O servidor de TFTP é o tftpd-hpa, embora haja outros.
apt-get install tftpd-hpa
E em princípio não é preciso mais nada. O TFTP deve ficar instalado e a funcionar. A diretoria que vai usar é a /srv/tftp. Portanto os ficheiros deve ser aí colocados.

2.3. Colocar os ficheiro de arranque do Debian

O CD de arranque do Debian contém um ficheiro netboot/netboot.tar.gz. Apenas é preciso descompactar este ficheiro na diretoria do TFTP (/srv/tftp).
Referências
http://www.debian.org/releases/stable/i386/ch04s05.html.pt
http://wiki.debian.org/DHCP_Server
http://http.us.debian.org/debian/dists/squeeze/main/installer-i386/current/images/netboot/

Partilhar ligação de internet

Partilhar a ligação de internet

Colocar uma máquina linux a partilhar a ligação de internet (desde que tenha pelo menos duas placas de rede, claro), é relativamente simples.
Aqui ficam as instruções para referência futura. Assume-se que wlan0 liga à internet e que eth0 liga à intranet.

Ligar o sistema de NAT (masquerade) na interface de ligação ao exterior:
iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE

Aceitar pedido de encaminhamento proveniente da interface "interior":
iptables -A FORWARD -i eth0 -j ACCEPT

Habilitar o forwarding para IPv4:
sysctl -w net.ipv4.ip_forward=1

Atribuir um endereço à interface externa. Este passo não é necessário de a interface já tiver um endereço.
ifconfig eth0 10.0.0.254/24

Reiniciar o serviço de rede:
/etc/init.d/networking restart

segunda-feira, 23 de julho de 2012

Desligar (spindown) os discos automaticamente

Desligar (spindown) os discos automaticamente

Esta dica funciona melhor em sistemas com mais de um disco rígido (por exemplo, um homeserver, como é o meu caso) ou num portátil.
A ideia é arranjar maneira de desligar o motor dos discos rígidos quando estes não estão a ser usados. Poupa-se energia, pois o disco consome menos com o motor desligado obviamente e pode prolongar-se o tempo de vida do disco. Para prolongar o tempo de vida do disco é preciso definir um tempo espera generoso antes de fazer o spindown, pois demasiados ciclos de desligar e reiniciar o motor vão ter o efeito contrário, isto é, diminuir o tempo de vida do disco.
Assim, vou usar 30 minutos como tempo recomendado.
Há um projeto aqui http://code.google.com/p/spindown/ de um daemon que faz exatamente o que se pretende, mas eu quero uma solução mais "caseira", que assente mais em bash e que não seja preciso andar a compilar código.
Vou usar Debian para este projeto, pois ando desanimado com o Ubuntu. Está cada vez mais parecido ao Windows.
É preciso executar todos os comandos como root.

1. Instalar o sg3-utils

apt-get install sg3-utils
O pacote sg3-utils tem o comando sg_start que podemos usar para parar o motor do disco.
Aparentemente também se pode fazer com o sdparm e com o hdparm, mas ainda não experimentei.

2. Criar um script

Criei um script em /root/bin/spindown.sh com o seguinte conteúdo:

# !/bin/sh
if [ "$(id -u)" != "0" ]; then
        echo "This script must be run as root" 1>&2
        exit 1
fi
if [ -z $1 ]; then
        echo Usage: $0 sdX
        exit 1
fi
# isto estava a causar alguns problemas, por enquanto fica desligado
#if ! [ -e /dev/$1 ]; then
#        echo /dev/$1 not found
#        exit 1
#fi
NEWFILE=/tmp/NewState.$1
OLDFILE=/tmp/OldState.$1
date +"%F %T"
# Get new state from diskstats
NEWstate=$(cat /proc/diskstats | grep $1)
echo $NEWstate > $NEWFILE

if [ -z $(diff OLDstate.txt NEWstate.txt) ]; then
        echo Stopping $1...
        # sdparm --flexible --command=stop /dev/$1 &>/dev/null
        sg_start --stop /dev/$1
else
        echo $1 active
fi



# Write current state to file
echo $NEWstate > $OLDFILE

Não esquecer de ligar o bit executável no ficheiro:
chmod +x spindown.sh

3. Adicionar ao crontab

Adicionar ao crontab a linha:
*/30 * * * * /root/bin/spindown.sh sdc
Deve ser adicionada uma linha por cada disco que se pretende controlar, mudando o 'sdc' de acordo com o disco, claro.

Como o script faz algumas verificações antes de ser executado, deve ser seguro de executar na linha de comandos para testes (sem garantias...),
Não é uma solução muito elegante, mas é relativamente simples.

Referências


quarta-feira, 25 de abril de 2012

Programar para Android - 08


Botões que lançam outras atividades

Começamos por criar um novo projeto Try06 e retirar a string hello world, para ficar com um projeto vazio, mas já com a atividade já criada.

Desta vez vamos fazer algo um pouco mais longo e vamos ter o primeiro contacto com intents, permissões, obter dados do GPS e do Wifi e editar o AndroidManifest.xml.

Vamos criar uma atividade com 6 botões que lançam atividades ou realizam outras ações, algumas mais simples outras mais complexas.

Começamos por criar seis strings no strings.xml com nomes app1 a app6 e com o conteúdo: Google, Calculadora, Notificação, Vibrar, GPS, Wifi. Os botões irão:

  • lançar o endereço http://www.google.pt;
  • lançar a calculadora;
  • mostrar uma notificação no écrã;
  • vibrar o telemóvel;
  • obter informação sobre o GPS;
  • obter informação sobre a ligação Wifi.

No main.xml vamos criar o layout. Adicionamos três LinearLayout horizontais dentro do LinearLayout vertical. Dentro de cada LinearLayout horizontal criamos dois botões, ficando assim com seis botões. Usando o botão "Distribute Weights Evenly", podemos fazer que os botões ocupem todo o écrã de forma equilibrada. O aspeto fica assim:
O xml do layout fica assim:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1" >

        <Button
            android:id="@+id/button1"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:onClick="runApp1"
            android:text="@string/app1" />

        <Button
            android:id="@+id/button2"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:onClick="runApp2"
            android:text="@string/app2" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1" >

        <Button
            android:id="@+id/button3"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:onClick="runApp3"
            android:text="@string/app3" />

        <Button
            android:id="@+id/button4"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:onClick="runApp4"
            android:text="@string/app4" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1" >

        <Button
            android:id="@+id/button5"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:onClick="runApp5"
            android:text="@string/app5" />

        <Button
            android:id="@+id/button6"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:onClick="runApp6"
            android:text="@string/app6" />
    </LinearLayout>

</LinearLayout>


Já não vamos precisar de mexer mais no strings.xml nem no main.xml.
Vamos agora ver o AndroidManifest.xml. Tal como o strings.xml e o main.xml, o Eclipse mostra uma interface gráfica, mas vamos editar diretamente o XML.
Este ficheiro contém informação sobre a aplicação que é usada, por exemplo, no Android Market (agora Play) para listar e identificar as aplicações.
Muitas das tags são óbvias. Desta vez vamos apenas acrescentar algumas linhas de texto, entre o <uses-sdk ...> e o <application ...>.
As linhas a acrescentar são estas:


    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    
    <uses-feature android:name="android.hardware.wifi" />
    <uses-feature android:name="android.hardware.location" />
    <uses-feature android:name="android.hardware.location.network" />
    <uses-feature android:name="android.hardware.location.gps" />


Os uses-permission listam as permissões que a aplicação precisa de ter para poder funcionar, neste caso precisa de poder ligar a vibração (VIBRATE), aceder aos dados de localização por GPS e pela rede (ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION) e aceder a informação da ligação sem fios (ACCESS_WIFI_STATE).
Esta informação também é mostrada ao utilizador no momento em que instala a aplicação e o utilizador tem de dar estas permissões à aplicação.
As linhas de uses-feature informam que a aplicação só irá funcionar em dispositivos que tenham o hardware indicado (neste caso wifi e GPS). Esta informação é usada no market para filtrar as aplicações que não são compatíveis com o dispositivo do utilizador.

Finalmente vamos ao código e graças à separação que o Android faz ao colocar o layout e permissões em ficheiros XML, só temos de nos preocupar mesmo com o código.
O método onCreate() que já existe não precisa ser mexido, apenas precisamos criar seis métodos novos, um para cada botão, com os nomes runApp1() a runApp6(). Estes foram os nomes definidos no main.xml.

O primeiro botão lança um browser para aceder ao endereço http://www.google.pt. Isso consegue-se com este código:


public void runApp1(View v) {
Intent i = new Intent(Intent.ACTION_VIEW, 
          Uri.parse("http://www.google.pt/"));
startActivity(i);
}

Um intent é um objeto que, basicamente, contém uma mensagem que é passada ao sistema. Neste caso o intent é criado com a mensagem (ação) ACTION_VIEW e com um parâmetro "http://www.google.pt". Um intent pode ter várias formas, mas este é uma das mais usadas. Contém dois parâmetros, o primeiro é a ação a executar (pode ser VIEW, PICK, EDIT, DELETE, etc) e o segundo parâmetro é um argumento para a ação. Neste caso estamos a dizer ao sistema que queremos ver (VIEW) o URL indicado. O sistema analisa o intent e escolhe a aplicação certa para executar a ação pedida. Pode haver mais que uma aplicação que possa executar essa ação, então o sistema pergunta ao utilizador qual a aplicação que pretende usar. Neste caso se houvesse dois browsers presentes no dispositivo o sistema iria perguntar que browser se pretendia usar.

O segundo botão lança a calculadora, usando também um intent.


 public void runApp2(View v) {
    Intent i = new Intent();
    i.setClassName("com.android.calculator2", "com.android.calculator2.Calculator");
    startActivity(i);
 }

Este intent é diferente. Começamos por criar um intent vazio e adicionamos um nome de classe. Este nome de classe é definido a partir do packagename da atividade. Por isso é importante que cada atividade tenha um nome único, porque esse nome vai ser utilizado para lançar a atividade e tem portanto de ser único.

O terceiro botão apenas mostra uma mensagem de notificação, que no Android é chamado um Toast.

public void runApp3(View v) {
    String msg = "Hello World, again!";
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
 
O quarto botão liga a vibração durante 1 segundo (1000 milisegundos). Usa o serviço de sistema VIBRATOR_SERVICE.

public void runApp4(View v) {
    Vibrator vib;
     vib = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
     vib.vibrate(1000);
}
 
O quinto botão obtém informação de localização do GPS ou da rede. Primeiro tenta obter a informação do GPS (GPS_PROVIDER). Se a informção não estiver disponível (GPS desligado ou sem sinal) pede informação à rede (NETWORK_PROVIDER). A informação obtida é mostrada num Toast.

public void runApp5(View v) {
    LocationManager locator = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    Location l = locator.getLastKnownLocation(LocationManager.GPS_PROVIDER);
    if (l == null) {
        // Fall back to coarse location.
        l = locator.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
    }
    double alt = l.getAltitude();
    double lat = l.getLatitude();
    double lon = l.getLongitude();
    long time = l.getTime();
    float acc = l.getAccuracy();
    float speed = l.getSpeed();
    String prov = l.getProvider();
    String msg =
       "Lat: " + lat + "\n" +
       "Long: " + lon + "\n" +
       "Alt: " + alt + "\n" +
       "Time: " + time + "\n" +
       "Accuracy: " + acc + "\n" +
       "Speed: " + speed + "\n" +
       "Provider: " + prov + "\n";
    Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}

O sexto e último botão obtém e mostra a informação sobre a ligação wifi atual. Usa o serviço de wifi do sistema (WIFI_SERVICE). A informação obtida também é mostrada num Toast.

public void runApp6(View v) {
    String msg;
    WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
  if (wifi.isWifiEnabled()) {
  msg = "Wifi ligado\n";
  WifiInfo winfo = wifi.getConnectionInfo();
 
  int ipadd = winfo.getIpAddress();
  String ip = String.format("%d.%d.%d.%d",
ipadd & 0xff,
ipadd >> 8 & 0xff,
ipadd >> 16 & 0xff,
ipadd >> 24 & 0xff);
  msg +=
"BSSID: " + winfo.getBSSID() + "\n" +
"IP: " + ip + "\n" +
"Speed: " + winfo.getLinkSpeed() + "\n" +
"MAC: " + winfo.getMacAddress() + "\n" +
"RSSI: " + winfo.getRssi() + "\n" +
"SSID: " + winfo.getSSID();
} else
msg = "Wifi desligado";
  Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}


O projeto necessita de uma lista relativamente longa de imports, que devem ser gerados pelo Eclipse, mas para referência aqui ficam:


import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Vibrator;
import android.view.View;
import android.widget.Toast;

Referências:

http://www.krvarma.com/2010/07/getting-ip-address-of-the-device-in-android/
http://www.damonkohler.com/2009/02/android-recipes.html
além do obrigatório: http://developer.android.com/reference/packages.html

segunda-feira, 23 de abril de 2012

Programar para Android - Índice



Programar para Android - 07


Um botão que muda um texto

Criamos um novo projeto Try05 da maneira habitual e apagamos a string do hello world, para começarmos com uma Activity já pronta, mas vazia.
Criamos uma string chamada tvText e com o conteúdo Número de clicks: e outra string com o nome btText e conteúdo Clica-me.
No layout (main.xml) adicionamos um botão e um TextView. Ao botão associamos o texto btText e à TextView associamos o texto tvText.
A ideia é que ao clicar no botão o texto vá indicando quantos clicks foram contados.

Vamos a Try05Activity e adicionamos a seguinte variável à atividade:

int numClicks = 0;

Esta variável vai contar os clicks e obviamente começa com 0 clicks.
De seguida criamos o seguinte método:


    private void updateText() {
    String s = getString(R.string.tvText) +  " " + numClicks;
    TextView tv1 = (TextView) findViewById(R.id.textView1);
    tv1.setText(s);    
    }

Este método atualiza o texto que pareca na TextView. Começa por definir uma string construída a partir do conteúdo da string tvText com um espaço e mais  número de clicks registados. Para podermos aceder ao texto na TextView textView1 (este é o nome dado à TextView criada no main.xml) precisamos de um objeto a que chamamos tv1 e fazemos a ligação ao texto já existente usando o método findViewById(). A última linha atualiza o texto.

Precisamos agora de um método que aumente o número de clicks cada vez que se clica no botão. Este método é muito simples de implementar. Vamos chamar-lhe processClick().


    public void processClick(View v) {
    numClicks++;
    updateText();
    }

Vamos fazer uma alteração ao método onCreate():


    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        updateText();
    }

Apenas foi adicionada a linha a negrito. Esta linha apenas corrige uma questão estética, faz aparecer o 0 (zero) quando o botão ainda não foi clicado nenhuma vez.

Se executarmos agora o código, não é apresentada nenhum erro, mas o programa não faz nada. Falta indicar que vai ser o método processClick() a processar o clicks no botão. Antes fizemos isso com o método setOnClickListener() do botão e tivemos de dizer que a atividade implementava a interface OnClickListener.
Desta vez vamos apenas ao ficheiro main.xml (diretamente ao XML) e acrescentamos esta linha no botão:

        android:onClick="processClick"

Esta linha indica que o método processClick() vai ser o responsável pelo processamento dos clicks neste botão. É claro que o nome do método pode ser outro qualquer, mas o método tem de ser public, não retornar valores (ou seja, ser void) e aceitar um parâmetro do tipo View.

Podemos assim no Android usar o XML para definir o layout da atividade e também para definir o comportamento de alguns componentes ao reagir a determinados eventos (como neste caso o click num botão). Assim na parte de código só fazemos mesmo código. Não é preciso fazer meio metro de código só para criar textos e botões e definir o seu comportamento. O Android permite separar o layout e comportamento para o XML e liberta o ficheiro de código para conter código "útil".


domingo, 22 de abril de 2012

Programar para Android - 06

Try04 - Um botão com ação.

Vamos começar por criar um novo projeto Try04, em que retiramos a mensagem de "Hello World".
Neste projeto vamos criar um botão com o id btButton que ocupe toda a área disponível do ecrã, ou seja, com FILL_PARENT na altura e na largura.
Tal como antes, temos o layout concluído, mas o programa não faz nada.
A ideia é que o botão apresente uma mensagem, quando for clicado mude para uma segunda mensagem e ao ser clicado de novo volte à primeira mensagem.
Vamos então criar duas strings em strings.xml, uma com o id btText e valor Clica-me e outra com id btTextClicked e valor Fui clicado.
Agora vamos ao código.


package pt.omeusite.try04;


import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

Os imports são feitos automaticamente pelo Eclipse, com o Ctrl+Shift+O.

public class Try04Activity extends Activity implements OnClickListener {

A atividade vai implementar a interface OnClickListener, para poder processar os clicks no botão. É possível definir uma nova classe ou uma subclasse para processar os clicks, mas é mais simples fazer que a atividade implemente o interface, pois apenas implica que tenha um método onClick().
Vamos precisar de um objeto do tipo Button e de uma variável inteira, para guardar o estado do botão.

Button btButton;
int status = 0;

/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

Começamos o código "a sério" por definir o objeto btButton. O objeto já foi definido no layout em main.xml, por isso agora apenas dizemos para ir buscar o objeto com o id btButton aos recursos.
De seguida definimos a própria atividade (this) como listener dos clicks.

        btButton = (Button) findViewById(R.id.btButton);
        btButton.setOnClickListener(this);
    }

O método que processa os clicks é este. Apenas muda o estado e define o texto do botão de acordo com esse estado.

    public void onClick(View v) {
    status = 1 - status;
    if (status==1)
    btButton.setText(R.string.btTextClicked);
    else
    btButton.setText(R.string.btText);
    }
}

E finalmente um programa que reage a alguma coisa.

Programar para Android - 05


Try03 - Layout com código Java.

Vamos agora tentar o mesmo layout, mas usando apenas Java.
Vamos criar o projeto Try03, e abrimos o Try03Activity.java, main.xml e strings.xml.
Primeiro vamos analisar o conteúdo.


package pt.omeusite.try03;


import android.app.Activity;
import android.os.Bundle;


public class Try03Activity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

A identificação do package e os imports são o normal do Java.
Apenas está definida a classe Try03Activity que extends a classe Activity. Dentro da nossa classe apenas temos um método onCreate(), que faz o override do onCreate() da classe Activity. O método onCreate() é chamado cada vez que uma atividade é criada, por isso é o local ideal para colocar o código que constrói a atividade (é como se fosse o construtor do objeto). A primeira ação é chamar o onCreate() da classe pai, passando-lhe o Bundle savedInstanceState. Deixemos a explicação do que é este Bundle para mais tarde. Fiquemos só com a noção que precisa estar lá.

A única outra instrução é um enigmático setContentView(R.layout.main), O setContentView() define qual é a view desta atividade (uma View, recordemos é um widget ou um layout), ou seja define o que vai aparecer no écrã. Neste caso passamos-lhe o argumento R.layout.main. O objeto R refere-se à diretoria res, o layout.main é uma referência ao ficheiro main.xml na diretoria layout. Ou seja, vai ler o nosso velho amigo main.xml! Por isso é que quando editamos o main.xml as alterações aparecem no écrã!
Percebido isto, vamos começar a apagar coisas:

  • no Try03Activity.java apagamos exatamente a linha setContentView(R.layout.main);
  • no main.xml apagamos a TextView;
  • no strings.xml apagamos a string hello.
Ficamos com uma aplicação que nada faz (semelhante ao que fizemos em Try00, mas agora há mais algum código no ficheiro .java).
A partir de agora não mexemos mais nos ficheiros XML.
O ficheiro .java fica assim:

package pt.omeusite.try03;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;

public class Try03Activity extends Activity {
    // estas views vão ser necessárias
    LinearLayout vLayout, hLayout;
    TextView tvTitle, tvName;
    EditText etEdit;
    Button btOk;

/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        /* estes layouts cumprem a mesma função do:
         * android:layout_width="..."
         * android:layout_height="..."
         */
        LinearLayout.LayoutParams lpFF = new LinearLayout.LayoutParams(
                LayoutParams.FILL_PARENT,
                LayoutParams.FILL_PARENT);
        LinearLayout.LayoutParams lpFW = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.FILL_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
        // esta layout acrescenta o valor 1.0f, que é o peso relativo da view
        LinearLayout.LayoutParams lpWW = new LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.WRAP_CONTENT,
        LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
        
        // criamos o layout vertical que será o topo da view
        vLayout = new LinearLayout(this);
        // definimos orientação como vertical
        vLayout.setOrientation(LinearLayout.VERTICAL);
        // definimos os parametros de layout como FILL_PARENT, FILL_PARENT
        vLayout.setLayoutParams(lpFF);

        // criamos uma nova TextView
        tvTitle = new TextView(this);
        // definimos o texto
        tvTitle.setText("Try03Activity");
        // definimos os parâmetros de layout
        tvTitle.setLayoutParams(lpFW);
        // adicionamos esta View ao layout vertical
        vLayout.addView(tvTitle);

        // criamos um novo layout
        hLayout = new LinearLayout(this);
        // definimos layout horizontal
        hLayout.setOrientation(LinearLayout.HORIZONTAL);
        // definimos os parâmetros
        hLayout.setLayoutParams(lpFW);
        
        // criamos uma nova TextView
        tvName = new TextView(this);
        // definimos o texto
        tvName.setText("Nome");
        // definimos os parâmetros
        tvName.setLayoutParams(lpWW);
        // adicionamos a View ao layout horizontal
        hLayout.addView(tvName);
       
        // criamos uma nos caixa de texto
        etEdit = new EditText(this);
        // definimos tamanho como 10 ems
        etEdit.setEms(10);
        // definimos layout
        etEdit.setLayoutParams(lpWW);
        // adicionamos ao layout horizontal
        hLayout.addView(etEdit);
       
        // adicionamos o layout horizontal ao vertical
        vLayout.addView(hLayout);
        
        // criamos um botão
        btOk = new Button(this);
        // definimos o texto
        btOk.setText("Ok");
        // definimos os parâmetros
        btOk.setLayoutParams(lpFW);
        // adicionamos o botão ao layout vertical
        vLayout.addView(btOk);
       
        // definimos a vista ativa como sendo o layout vertical que acabámos de definir
        // e o layout é mostrado no écrã
        setContentView(vLayout);
    }
}

Acho que não há melhor motivação para usar o editor gráfico ou o XML.
O programa continua a não fazer absolutamente nada, mas foi preciso escrever uma quantidade considerável de código para isso. Mesmo com as ajudas do Eclipse (Ctrl+Shift+O para adicionar os imports automaticamente e Ctrl+Space para ajudar na conclusão das palavras) não é um exercício trivial.
Por isso normalmente as interfaces gráficas não são criadas usando código. Há alturas em que é inevitável, como por exemplo preencher uma lista pendente (Spinner, na nomenclatura Android) com dados retirados de uma base de dados.

Apesar de funcionar, o programa tem um erro conceptual: as strings estão hardcoded na atividade, o que já tínhamos dito que não se deve fazer. Para usar os id das strings é preciso primeiro criá-las no ficheiro strings.xml (ou noutro ficheiro XML, não é obrigatório ser no strings.xml).
Afinal sempre temos de editar o XML. Criamos então as seguintes strings no strings.xml, usando o editor ou diretamente no XML:

<string name="tvTitle">Try02Activity</string>
<string name="tvName">Nome</string>
<string name="btOk">Ok</string>

Notar que para identificador de cada string foi usado o mesmo nome que para o objeto no código Java. Isto NÃO é obrigatório, mas ajuda a manter a sanidade do programador.

Agora precisamos alterar as linhas do código que se referem às strings:

(...)
tvTitle.setText(getString(R.String.tvTitle));
(...)
tvName.setText(getString(R.string.tvName));
(...)
btOk.setText(getString(R.string.btOk));
(...)

E pronto, um programa que não faz nada, mas arrumadinho.

Programar para Android - 04


Try02 - Layout com XML.

Vamos criar um layout exatamente idêntico ao criado no Try01, mas usando apenas os ficheiros XML diretamente.

Começamos por criar um projeto Android chamado Try02.
Por uma questão de criação de bons hábitos vamos abrir os ficheiros Try02Activity.java, res/layout/main.xml e res/values/strings.xml, mas na verdade nem sequer vamos mexer no .java.
Mudamos o main.xml e strings.xml para a vista de XML.

Ao contrário do modo de edição gráfico, em que vamos fazendo as coisas à medida que nos lembramos, do tipo "ah, preciso aqui duma string, click direito e adiciono-a", o modo XML exige que se pense um pouco antes de começar a editar.
Neste caso já sabemos bem o que pretendemos, por isso podemos ir diretamente ao ficheiro strings.xml e adicionar as strings que precisamos, que são:

  • uma string tvName com o valor "Nome", para aparecer como legenda de uma caixa de texto;
  • uma string btOk com o valor "Ok", para aparecer no botão.
A estrutura do strings.xml é bastante simples de perceber e para adicionar as duas strings basta acrescentar duas linhas dentro do resources ... /resources. Fica assim (as linhas adicionadas estão a negrito):

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="hello">Try02Activity</string>
    <string name="app_name">Try02</string>
    <string name="tvName">Nome</string>
    <string name="btOk">Ok</string>

</resources>

Também mudamo a mensagem de "Hello World" para aparecer apenas o nome da atividade.

O main.xml já dá mais um pouco de trabalho, mas nada de muito difícil.
Temos de pensar primeiro na estrutura que queremos criar. No topo tem de existir sempre um layout, que no nosso caso é um LinearLayout vertical. Dentro dele vamos colocando os outros elementos e outros layouts. Assim:
  • LinearLayout vertical
    • TextView "Try02Activity"
    • LinearLayout horizontal
      • TextView "Nome"
      • TextField editText1
    • Botão "Ok"
O main.xml criado pelo Eclipse já tem o layout vertical e a primeira TextView, só temos de acrescentar o resto, usando o que já lá está como guia (as linhas acrescentadas estão a negrito):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/tvName" />

        <EditText
            android:id="@+id/editText1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="10" />

    </LinearLayout>

    <Button
        android:id="@+id/button1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btOk" />

</LinearLayout>

Algumas coisas são óbvias, outras nem por isso.

Um elemento <LinearLayout> precisa sempre de um </LinearLayout> a fechar, porque um layout pode conter um número indeterminado de elementos, mas um elemento TextView, EditText, Button, etc não precisa da tag a fechar.

Todos os elementos (layouts ou não) precisam de pelo menos as propriedades android:layout_width e android:layout_height definidas. O valor fill_parent indica que o elemento deve estender-se para encher a área disponível no elemento "pai". O valor wrap_content indica que o elemento deve ocupar apenas o espaço necessário.

Os elementos gráficos precisam de um android:id. Observamos que todos os id aqui presentes começam com um @ e que as views (é como são conhecidos os widgets no Android) têm um @+, enquanto que as strings têm um @ sozinho.
O @ indica que não queremos que o que está escrito dentro das "" seja escrito literalmente, mas que seja procurada a referência nos recursos (ou seja algures em res/). No caso das strings o @string/btOk significa "procura nas strings uma com o id btOk". No caso das views o @+id/button1 significa "cria um identificador para esta view chamado button1". Ou seja o + é usado para criar um novo id. Se houver mais tarde uma nova referência a este botão bastaria usar @id/button1.
Também é possível referenciar recursos disponibilizados pelo próprio Android com @android:id/qq_coisa, mas lá iremos.

Se os elementos tiverem algum conteúdo de texto este é definido com android:text. Podemos fazer uma pequena experiência que é acrescentar a linha android:text="Escreva o seu nome" ao EditText. Se corrermos agora a aplicação aparece o texto "Escreva o seu nome" na caixa de edição. Já sabemos que não é boa ideia ter strings hardcoded na aplicação, por isso se quisermos realmente que apareça uma mensagem devemos criar uma string no strings.xml e colocar aqui o seu id.

A propriedade android:ems="10" do EditText parece um pouco mais obscura. Define a largura mínima do campo, que neste caso é de 10 ems. E o que é um "ems"? É a largura da letra mais larga, que é a letra "M", daí o nome. Ou seja, é o número de M's que cabem no campo.

E cá está, mais um programa que não faz nada, sem escrever uma única linha de código.