quarta-feira, 8 de dezembro de 2010

Java ME: Configuração do Ambiente

Olá povo,

Nessa sexta começo minha carreira como professor universitário :) Ministrarei a disciplina de Java ME no Curso de Especialização em Engenharia de Software com Ênfase em Tecnologias para Desenvolvimento de Aplicações Móveis do CESAR.edu. Então como vocês devem imaginar, vão vir por aí alguns post sobre o assunto. O primeiro deles será sobre como configurar o ambiente e fazer o bom e velho "Hello World".

Baixando o JDK

Precisaremos incialmente do JDK que está disponível em:
http://www.oracle.com/technetwork/java/javase/downloads/index.html

A instalação em bem simples, basta seguir o assitente. (e venhamos e convenhamos, todos que estão lendo este post já devem ter o JDK na máquina :)

Baixando o WTK (Wireless Toolkit)

Para baixar o WTK devemos acessar a seguinte URL:
http://www.oracle.com/technetwork/java/javame/downloads/index.html

A versão mais recente da plataforma é a 3.0, mas atualmente ela só está disponível para Windows. Dessa forma, utilizaremos a versão 2.5.2 que está disponível para Windows e Linux.


Após baixar o WTK, basta seguir as instruções do assistente.


Instalando a IDE

Para desenvolvermos aplicações Java ME, podemos utilizar a IDE de nossa preferência. As mais famosas são o Eclipse e o Netbeans. Ambas contam com plugins para desenvolvimento para edição móvel de Java.


O Eclipse conta com uma versão especial para desenvolvimento mobile chamada Pulsar. Ela já vem com as ferramentas necessárias para criação de aplicações Java ME, e para baixá-la basta acessar: http://www.eclipse.org/pulsar/
e descompactar em qualquer lugar do seu HD.

Mas para quem já tem seu Eclipse já todo configurado, não precisa se preocupar em instalar outra versão da IDE. Basta adicionar o plugin MTJ (Mobile Tools for Java) e você terá um ambiente pronto para criar suas aplicações Java ME. Para isso, acesse o menu Help > Install new software e adicione a URL: http://download.eclipse.org/mtj/updates/1.1.2/stable e em seguida prosseguir com o assitente.


Para os desenvolvedores que utilizam o Netbeans, a IDE já pode ser baixada com o Mibility Pack. Que permite a criação de aplicativos Java ME. Caso sua versão não conte com esse plugin, basta baixa-lo dentro da própria ferramenta acessando o menu Tools > Plugins e selecionando o plugin Mobility.

Configurando a IDE para o SDK

Uma vez que temos o SDK e a nossa IDE favorita instalados, devemos fazer a ligação de ambos.

Para o Eclipse faremos o seguinte:
1) Acesse o menu Window > Preferences;
2) Clique em Java ME e em seguida preencha o campo WTK home com o local onde o WTK está instalado;
3) Na mesma janela, clique em Device Management que é um subitem de Java ME. Clique no botão Manual Install... para informar os emuladores que iremos utilizar.
4) Abrirá a janela Manual Device Installation, clique no botão Browse... e selecione o diretório de instalação do WTK.
5) Clique em Finish para fechar a tela atual e OK para fechar a tela de configuração.

Para o Netbeans:
1) Acesse o menu Tools > Java Plaforms;
2) Clique em Add Platform. Em seguida, selecione Emulator for Java ME Platform e clique em Next;
3) O assistente deve achar o local da instalação automaticamente, caso contrário indique o local da instalação e clique em Next.
4) Clique em Finish e a configuração estará concluída.


Hello World!!!

Vamos agora satisfazer os deuses da programação e fazermos um HelloWorld com a plataforma Java ME.

Eclipse
1) Acesse o menu File > New > Project...
2) Em seguida, selecione Java ME > MIDlet Project
3) Dê o nome de HelloWorld ao projeto e clique em Finish
4) Na pasta src clique com o botão direito, e selecione New... > Java ME MIDlet
5) Abrirá o assitente de criação de classes. Dê a classe o nome de HelloWorldMIDlet e clique em Finish

Netbeans
1) Acesse o menu File > New project...
2) Na categoria Java ME, selecione Mobile Application
3) Dê o nome de HelloWorld ao projeto.
O Netbeans já disponibiliza uma opção que cria o HelloWorldMIDlet (estraga prazer:) Desmarque essa opção e clique em Finish.
4) Na pasta src clique com o botão direito, e selecione New... > MIDlet...
5) Abrirá o assitente de criação de classes. Dê a classe o nome de HelloWorldMIDlet e clique em Finish

Agora, deixe a classe HelloWorldMIDlet conforme abaixo:
public class HelloWorldMIDlet extends MIDlet {
public HelloWorldMIDlet() {
Display d = Display.getDisplay(this);

Form form = new Form("Hello!");
form.append("Olá mundo Java ME");

d.setCurrent(form);
}

protected void destroyApp(boolean unconditional) {}

protected void pauseApp() {}

protected void startApp() {}
}


No Eclipse, clique com o botão direito sobre o projeto e selecione: Run as > Emulated Java ME JAD.
No Netbeans, basta clicar no botão play verdinho :p

Quando o emulador for executado, clique na tecla do celular correspondente a opção Launch. O resultado deve ser conforme a figura abaixo:



Qualquer dúvida ou sugestão, deixem seus comentários.

4br4ç05,
nglauber

sábado, 4 de dezembro de 2010

Enviando e Recebendo SMS no Android

Olá povo,

Questionar é uma coisa muito sábia. Quando estava dando aula sobre SMS um aluno (Renato se não me engano) questionou meu código, que na verdade estava muito similar (pra não dizer igual) ao código do livro de Ricardo Lecheta. Quando paramos pra analisar vimos como ficou fácil interceptar um SMS.

Primeiro devemos criar um BroadcastReceiver e registrá-lo no AndroidManifest.xml para ser disparado quando a ação "android.provider.Telephony.SMS_RECEIVED" for disparada pelo sistema operacional. Além disso, deve-se utilizar as permissões para enviar e receber SMS.


public class SmsReceiver extends BroadcastReceiver {

public void onReceive(Context context, Intent it){
SmsMessage message =
SMSUtils.getMessagesFromIntent(it)[0];

Toast.makeText(context,
"Mensagem recebida de "+
message.getOriginatingAddress() + " - "+
message.getMessageBody(),
Toast.LENGTH_LONG).show();
}
}


<receiver android:name="SmsReceiver">
<intent-filter>
<action
android:name="android.provider.Telephony.SMS_RECEIVED"/>
<category
android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>

<!-- Adicione essas permissões na tag manifest -->
<uses-permission
android:name="android.permission.RECEIVE_SMS"/>
<uses-permission
android:name="android.permission.SEND_SMS"/>



public class SMSUtils {

public void enviarSms(String numero, String msg) {

SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(numero, null, msg,
null, null);
}

public static SmsMessage[] getMessagesFromIntent(
Intent intent){

Object[] pdusExtras =
(Object[])intent.getSerializableExtra("pdus");

SmsMessage[] messages =
new SmsMessage[pdusExtras.length];

for (int i = 0; i < pdusExtras.length; i++) {
messages[i] = SmsMessage.createFromPdu(
(byte[])pdusExtras[i]);
}
return messages;
}

}// end class


O Android utiliza o formato de mensagens em PDU que permite a leitura de SMS em vários encondings. Por isso, ao receber as mensagens, lemos a informação extra da intent chamada de "pdus". Essa informação é um array de arrays de bytes. Percorremos o array de bytes (que em cada posição tem outro array) e passamos cada array para um método que cria a mensagem SMS. Uma vez com o objeto android.telephony.SmsMessage, podemos obter as informações como telefone de origem e texto da mensagem.

Quando a mensagem é recebida ela vai para a caixa de entrada. Para excluírmos a mensagem recebida, basta usar o content provider da aplicação de SMS conforme explicado nesse post

Se quisermos evitar que a mensagem chegue na aplicação de mensagens, basta colocar uma prioridade (android:priority) maior do que a da aplicação de mensagens no intent-filter do Broadcast que recebe o SMS. E dentro dele, chamar o método abortBroadcast().

4br4ç05,
nglauber

sexta-feira, 3 de dezembro de 2010

Artigo "Android Froyo"

Olá povo,

A revista Web Mobile desse mês (edição 33) trás uma matéria sobre a versão 2.2 do Android de condinome Froyo. Essa matéria foi escrita por mim em parceria e por meus colegas Bruno Vinícius (da Gluo Games) e José Berardo (da Especializa Treinamentos) ,

Neste artigo apresentamos as novidades da versão 2.2 do sistema operacional para dispositivos móveis da Google. O Android Froyo vem para encarar e superar seus concorrentes através de várias melhorias de desempenho e diversas novas funcionalidades para os usuários dos dispositivos que utilizam a plataforma.

Espero que vocês gostem.

4br4ç05,
nglauber

p.s.: essa matéria foi escrita logo quando saiu o Froyo, mas só foi publicada agora :)

quinta-feira, 2 de dezembro de 2010

Android: Dicas de programação da semana

Olá povo,

No novo projeto em que estou envolvido estamos desenvolvendo várias aplicações. Como temos várias pessoas envolvidas, algumas dúvidas se tornam recorrentes, então resolvi postá-las.

Pergunta 1: Quando eu giro o telefone o método onCreate da Activity é chamado novamente. Tem como evitar isso?

Resposta: Sim, basta adicionar a propriedade android:configChanges na tag <activity> do seu AndroidManifest.xml. Isso evitará que a atividade seja recriada.

<activity android:name="MinhaActivity"
android:configChanges="keyboardHidden|orientation"/>

No caso acima, se o telefone mudar de orientação ou se o teclado físico (se houver) for aberto, a atividade não será recriada.

Pergunta 2: Tem como deixar uma atividade apenas em landscape ou portrait?

Resposta: Sim, basta utilizar a propriedade android:screenOrientation na tag <activity> do seu AndroidManifest.xml.

<activity name=".MinhaActivity"
android:screenOrientation="landscape"/>


Pergunta 3: Como permitir apenas letras na caixa de texto (EditText)?

Resposta: Não existe uma propriedade para fazer isso. Se quisermos colocar para aceitar apenas números podemos utilizar a propriedade android:inputType="number". Ou quando queremos deixar todas as letras em maiúsculo podemos usar o valor textCapCharacters. Mas para não aceitar nem números nem símbolos só com a boa e velha expressão regular.

Pattern pattern = Pattern.compile(
"^[A-ZÁÀÄÃÂÆÅÉÈËÊÍÌÏÎÓÒÖÕÔØŒÚÙÜÛÝÿYÑÇ ]+$");

Matcher matcher = pattern.matcher(
meuEditText.getText().toString().toUpperCase());

if (!matcher.matches()){
Log.e("ERRO",
"Esse campo só aceita letras e espaço em branco!");
}


Pergunta 4: Como fazer uma lista múltipla que eu possa selecionar vários itens?

Resposta: Devo confessar que esse problema me encheu muito o saco. Não para criar a lista, mas sim para obter os itens selecionados. Pra informar que sua ListView permite seleção múltipla use o método setChoiceMode(int).

lstView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

Feito isso, sua lista já permitirá seleção múltipla. Agora como fazer para obter os itens selecionados? Bem, a classe ListView tem dois métodos que você pode obter arrays com as posições e os ids dos itens selecionados: getCheckItemPositions() e getCheckItemIds(). Infelizmente não tive uma boa experiência com eles. Ao marcar e desmarcar itens, aconteciam momentos em que os itens ficavam inconsistentes, ou seja, ele informava que itens estavam marcados, quando na verdade não estavam. Sendo assim, preferi fazer da minha forma:

private int getCheckedCount(){
lstView.getCheckItemIds();

int count = 0;
int listSize = lstView.getAdapter().getCount();
for (int i = 0; i < listSize; i++) {
if (lstView.isItemChecked(i)){
count++;
}
}
return count;
}

// Para marcar ou desmarcar um item específico,
// podemos usar:
lstView.setItemChecked(posicao, false);


Pergunta 5. Como carregar uma imagem da aplicação baseada no nome do arquivo?

Resposta: Se esse recurso estiver na pasta res/drawable você pode fazer via reflection. Eu já fiz um post sobre isso, que vocês podem conferir aqui. Mas se quiser usar de maneira descente :) podem usar a pasta assets do seu projeto. Coloque as imagens que você deseja carregar dinamicamente nesse diretório e faça como abaixo:

try {
InputStream is = getAssets().open("imagem.png");
Bitmap bmp = BitmapFactory.decodeStream(is);
ImageView img = (ImageView)
findViewById(R.id.ImageView01);

img.setImageBitmap(bmp);

} catch (IOException e) {
e.printStackTrace();
}


Pergunta 6. Como salvar uma informação simples como o recorde do jogo?

Resposta: Para persistir informações simples, o Android disponibiliza a classe SharedPreferences onde você pode salvar pequenas configurações. Vejam abaixo como ler/escrever uma preferência através de dois métodos de exemplo que salvam e recuperam o recorde de pontos de um jogo.

public int getRecord(){
SharedPreferences prefs =
context.getSharedPreferences(
"configs", Context.MODE_PRIVATE);

return prefs.getInt("record", 0);
}

public void setRecord(int record){
SharedPreferences prefs =
context.getSharedPreferences(
"configs", Context.MODE_PRIVATE);

SharedPreferences.Editor editor = prefs.edit();
editor.putInt("record", record);
editor.commit();
}


Pergunta 7. Como ler um XML do diretório res/xml?

Resposta: Quem é meu aluno já conhece essa frase que eu uso sempre nas aulas: Existem mil maneiras de se fazer Neston :) Uma das maneiras de ler um XML é usando o XMLResourceParser, segue um exemplo bem simples do seu uso. Clique aqui pra ver o post original.


private String getEventsFromAnXML(Context c)
throws XmlPullParserException, IOException {

StringBuffer stringBuffer = new StringBuffer();
Resources res = c.getResources();
XmlResourceParser xpp = res.getXml(R.xml.meu_xml);
xpp.next();

int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT){
if(eventType == XmlPullParser.START_DOCUMENT) {
stringBuffer.append("--- Start XML ---");

} else if(eventType == XmlPullParser.START_TAG){
stringBuffer.append("\nSTART_TAG: "+
xpp.getName());

} else if(eventType == XmlPullParser.END_TAG){
stringBuffer.append("\nEND_TAG: "+
xpp.getName());

} else if(eventType == XmlPullParser.TEXT) {
stringBuffer.append("\nTEXT: "+xpp.getText());
}
eventType = xpp.next();
}
stringBuffer.append("\n--- End XML ---");
return stringBuffer.toString();
}

De acordo com a evolução do projeto vou colocando mais dicas.

4br4ç05,
nglauber

quinta-feira, 25 de novembro de 2010

Defesa do Mestrado

Olá povo,

Depois de mais de 1 ano e 8 meses, quarta-feira (24/11/2010) eu defendi meu mestrado. O trabalho tinha como tema iOS2Droid - Uma ferramenta de tradução de aplicações iPhone para Android e teve orientação do professor Silvio Meira e co-orientação do professor left. A banca examinadora foi composta pelos Professores Carlos Ferraz e Felipe Furtado.

Tô colocando esse post, para que depois de tanto trabalho e esforço, eu possa lembrar como foi legal não só esse dia, mas o curso como um todo.


Fábrica Sofistic
No mestrado profissional do CESAR.edu os assuntos das disciplinas cursadas são aplicadas em um problema real de um cliente, que a fábrica de software (formada pelos próprios alunos que são divididos em grupos) deve solucionar.
A divisão dos grupos é feita pelo próprio pessoal do CESAR.edu, e infelizmente fiquei em uma fábrica na qual não me identifiquei com o problema a ser resolvido. Mas graças a Deus, tive a iniciativa de pedir pra mudar de fábrica, e fui aceito pela Sofistic. ValeuVivi, Alessandra, Jonas e Enderson!



Enderson


Alessandra


Jonas


Viviane


Banca de Negócios (depois, óbvio...)
Após pagar (quase) todas as disciplinas, temos que defender o produtos desenvolvido durante o curso para uma banca formada por especialistas em negócios.






Quando você termina de cursar todas as disciplinas, você pensa: "Ótimo! Agora é SÓ escrever a dissertação. Vai ser tranquilo". Amarga ilusão. Mas o dia chega...

O dia da defesa...

Comentário no Twitter (teve mais dois ou três :)


Dissertação pronta e impressa (pra apresentação)


Antes da apresentação...


Reconhecimento não tem preço. Valeu Felipe!

Agradeço a todos que me ajudaram (e que me aguentaram) direta ou indiretamente nessa caminhada. Ela ainda não terminou, pois preciso fazer as alterações solicitadas pela banca, mas com certeza, me sinto bem mais leve agora.

Quem quiser dar uma conferida na dissertação, é só clicar aqui.

4br4ç05,
nglauber

segunda-feira, 22 de novembro de 2010

TabHost, TabWidget e TabActivity

Olá povo,

Dar aulas é um desafio bem grande, e às vezes você paga uns micos esquecendo coisas simples. Esse post é dedicado aos meus alunos da LinuxFI em João Pessoa, aos quais presenciaram mais um mico proporcionado por minha pessoa.
Estava explicando como se fazia para criar uma tela com abas (ou guias, como preferir) no Android, e como não queria fazer como estava no livro, fui fazer de outro jeito. Aí claro que deu merda. Então implementei de outro jeito e prometi que iria fazer esse post. No final acabou que um aluno (perdão por não lembrar do nome) Raniere Fernandes descobriu o que estava faltando (era um maldito tabHost.setup()), mas aí já era!

Então vamos lá! Vou mostrar primeiro a maneira mais simples. Primeiro vou criar dois arquivos de layout: aba1.xml e aba2.xml.


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/raizAba1">

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Nome:" />
<EditText
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:id="@+id/edtNome"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Email"/>
<EditText
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:id="@+id/edtEmail"/>
</LinearLayout>


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/raizAba2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Observações:"/>
<EditText
android:id="@+id/edtObs"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:gravity="top"/>
</LinearLayout>

Notem que o elemento raiz do XML tem os identificadores raizAba1 e raizAba2. Esses identificadores serão utilizados para setar o conteúdo das abas.
Vamos agora ao código que configura a tela.

public class ExTabActivity1 extends TabActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

TabHost tabHost = getTabHost();

LayoutInflater.from(this).inflate(
R.layout.aba1, tabHost.getTabContentView());

LayoutInflater.from(this).inflate(
R.layout.aba2, tabHost.getTabContentView());

TabSpec aba1 = tabHost.newTabSpec("foo1");
aba1.setIndicator("Informações");

aba1.setContent(R.id.raizAba1);

TabSpec aba2 = tabHost.newTabSpec("foo2");
aba2.setIndicator("Observações");
aba2.setContent(R.id.raizAba2);

tabHost.addTab(aba1);
tabHost.addTab(aba2);
}
}

A classe TabActivity da qual nossa classe está herdando facilita o nosso trabalho para telas que usam abas. No método onCreate estamos obtendo a instância do TabHost que é o gerenciador de layout responsável por esse tipo de tela. Internamente ele contém um objeto TabWidget e um FrameLayout que exibe o conteúdo das abas (como veremos no próximo exemplo).

Cada aba é representada por um objeto do tipo TabSpec, que é adicionado ao TabHost. Para criar uma TabSpec chamamos o método newTabSpec do objeto TabHost passando uma tag de identificação. Para definir o texto de cada aba chamamos o método setIndicator. E para setar o conteúdo, carregamos o arquivo de layout através da chamada:

LayoutInflater.from(this).inflate(
R.layout.aba1, tabHost.getTabContentView());

Isso carregará o arquivo de layout no FrameLayout interno do TabHost. Uma vez que o layout está carregado, podemos definir o conteúdo da TabSpec, chamando o método setContent e passando o id da view raiz do arquivo de layout. No caso, R.id.raizAba1 para primeira aba e R.id.raizAba2 para a segunda.

Mas e se eu quiser ter mais do que uma TabHost na minha tela? Como, por exemplo, um botão no canto inferior? Pois é, esse era o exemplo que eu queria ter terminado em "Jampa".
Primeiro devemos criar um arquivo de layout igual ao definido abaixo, e a este, chamaremos de meuTabHost.xml:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">

<TabHost
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/meuTabHost"
android:layout_weight="1">
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TabWidget
android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>

<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@android:id/tabcontent"/>
</LinearLayout>
</TabHost>

<Button
android:text="Salvar"
android:id="@+id/Button01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>


Notem que nele, dentro de um LinearLayout, temos um TabHost, que dentro dele temos um TabWidget e um FrameLayout, conforme já tínhamos descrito. Uma atenção especial nos ids do TabHost e do TabWidget, que devem, obrigatoriamente chamar-se @android:id/tabs e @android:id/tabcontent respectivamente.

Para finalizar, a classe Java ficaria conforme abaixo:


public class ExTabActivity2 extends Activity {

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.meutab);

TabHost tabHost = (TabHost)findViewById(
R.id.meuTabHost);

tabHost.setup();

// O resto é igual ao exemplo anterior
}
}


Agora a nossa classe herda de Activity e não mais de TabActivity. Dessa forma, temos que chamar o método setContentView passando o nosso layout e chamar o (MALDITO) método setup do objeto TabHost, que agora é obtido através do arquivo de layout.

Aê turma da LinuxFI, um "Aleluia" pro professor! :)

4br4ç05,
nglauber

sexta-feira, 19 de novembro de 2010

Android ou iPhone? Que tal os dois?

Olá povo,

Na próxima quarta-feira 24/11 estarei defendendo minha dissertação de mestrado. Meu tema trata de como fazer com que uma aplicação iPhone execute na plataforma Android. Para isso, desenvolvi uma ferramenta chamada iOS2Droid, que converte o código fonte escrito em Objective-C (linguagem do iPhone) para Java (usado no Android). Essa ferramenta se torna muito útil principalmente para empresas de desenvolvimento que já criam aplicações para iPhone e querem converter as mesmas para a plataforma do Google.

Então uma das seções que apresentarei na minha defesa, são os concorrentes do iOS2Droid, que vou listar pra vocês abaixo:


XMLVM (http://www.xmlvm.org) é um trabalho realizado por Arno Puder que realiza a conversão de arquivos de linguagem intermediária (como os .class do Java) para um arquivo XML, que por sua vez pode ser convertido para outra linguagem.
Um dos recursos do XMLVM é a possibilidade de converter aplicações Android para iPhone. Exatamente o oposto do que é proposto pelo iOS2Droid. Um detalhe é que a aplicação desenvolvida em Android, utiliza o framework do iPhone escrito em Java.

Dessa forma, um diferencial do iOS2Droid é que a aplicação desenvolvida em iPhone utiliza o framework nativo, e a ferramenta faz as adaptações para converte-la em uma aplicação Android.


O PhoneGap (http://www.phonegap.com/) é um framework de desenvolvimento open source para construir aplicações mobile cross-platform. As aplicações são construídas utilizando HTML e JavaScript e permite que uma aplicação possa executar em diversas plataformas além de Android e iPhone, como Palm, Symbian e Blackberry.


O Titanium Mobile (http://www.appcelerator.com/products/titanium-mobile-application-development/) faz trabalho similar ao PhoneGap. O desenvolvedor cria as aplicações utilizando HTML/JavaScript/CSS e a mesma poderá ser executada em telefones Android ou no iPhone.
A ferramenta tem uma versão gratuita com funcionalidade reduzida e uma paga (com todas as funcionalidades obviamente).

Um ponto positivo do iOS2Droid em relação a essas duas ferramentas, é que o desenvolvedor não tem que aprender um novo framework de desenvolvimento.


Com o Adobe Air (http://www.adobe.com/br/products/air/) os desenvolvedores podem criar aplicações com o Flash CS5 para dispositivos móveis como: iPhone, Android e BlackBerry. Assim como outras ferramentas já citadas, pode-se utilizar HTML+CSS+JavaScript, porém o desenvolvedor pode contar também com a linguagem ActionScprit.


Para finalizar, temos o Elips Studio (http://www.openplug.com/products/elips-studio) que é um SDK baseado no Adobe Flex, onde o desenvolvedor cria suas aplicações usando ActionScript e MXML, e as aplicações serão disponibilizadas para Android e iPhone através de uma engine que converte esse código em uma aplicação nativa.

Podemos notar que todos os concorrentes apresentados aqui se baseiam em novas aplicações. E todas aquelas aplicações disponíveis na AppStore? O iOS2Droid pensou nelas! A ferramenta converte um código existente em uma aplicação Android. E os desenvolvedores de aplicações para iPhone poderão continuar utilzando o seu bom e velho Xcode. Podendo agora, ver suas aplicações executando em dispositivos Android com pouquíssimo trabalho.

Espero que a banca goste do trabalho. E vocês o que acham da idéia? Quem quiser dar uma conferida na dissertação é só clicar aqui.

4br4ç05,
nglauber

quarta-feira, 3 de novembro de 2010

Hello World Samsung bada

Olá povo,

Pra quem pensa que nesse blog só sai artigo sobre Android... Vocês tem uma grande parcela de razão :) Então hoje vou variar um pouquinho e falar sobre a plataforma bada da Samsung. Depois faremos um "Hello World"!

bada é o sistema operacional desenvolvido pela Samsung projetado para cobrir tanto os celulares "top de linha" quanto celulares mais modestos da compania. A empresa afirma que bada irá rapidamente substituir sua plataforma proprietária para "feature phones", transformando esse tipo de aparelho em smartphones.

Para iniciar o desenvolvimento com a plataforma devemos acessar http://developer.bada.com, se registrar gratuitamente e fazer o download do SDK. O instalador do SDK é bem simples, e o bacana é que diferentemente do Android, não precisamos baixar o Eclipse, baixar SDK, instalar plugin... Tudo é instalado e configurado junto.

A IDE é baseada em Eclipse e é uma extensão do CDT que permite o desenvolvimento C/C++ . Dentro da IDE temos compilador, debugger e a visualização da aplicação através de um simulador.

Vamos criar o primeiro projeto. Acesse o menu File > New > bada Application Project, digite "HelloWorld" no nome do projeto, selecione "bada Form Based Application", e clique em "Finish" (existem diversas configurações se clicar em Next, mas vou deixar pra um próximo post). Será criada a seguinte estrutura do projeto:


  • Include: bibliotecas do bada;
  • inc: ficarão os arquivos de cabeçalho (extensão .h);
  • src: arquivos de implementação (extensão .cpp);
  • Home: arquivos de escrita e leitura utilizados pela aplicação;
  • Res: Arquivos de recursos.
Abra o arquivo IDF_FORM1.xml. Ele terá um botão com o texto "OK". Arraste um componente "Edit Field" logo acima do botão e um "Label" abaixo.
Agora, abra o arquivo Form1.h e deixe a seção "protected" como abaixo:


// Implementation
protected:
static const int ID_BUTTON_OK = 101;
Osp::Ui::Controls::Button *__pButtonOk;
Osp::Ui::Controls::Label *__meuLabel;
Osp::Ui::Controls::EditField *__meuEdit;


No arquivo Form1.cpp, fica a implementação do comportamento da classe. No método OnInitializing, vamos realizar a ligação entre o código-fonte e os componentes que adicionamos ao formulário. Deixe-o conforme abaixo:


result
Form1::OnInitializing(void)
{
result r = E_SUCCESS;

// TODO: Add your initialization code here

// Get a button via resource ID
__pButtonOk = static_cast<Button *>
(GetControl(L"IDC_BUTTON_OK"));
__meuLabel = static_cast<Label *>
(GetControl(L"IDC_LABEL1"));
__meuEdit = static_cast<EditField *>
(GetControl(L"IDC_EDITFIELD1"));

if (__pButtonOk != null)
{
__pButtonOk->SetActionId(ID_BUTTON_OK);
__pButtonOk->AddActionEventListener(*this);
}
return r;
}


Notem que inicializamos os componentes com o método GetControl passando uma String. Essa String representa o ID do componente. Depois, associamos um evento ao botão, que é tratado pela própria classe pelo método OnActionPerformed. Deixe-o conforme abaixo:


void
Form1::OnActionPerformed(const Osp::Ui::Control& source, int actionId)
{
switch(actionId)
{
case ID_BUTTON_OK:
{
AppLog("OK Button is clicked! \n");

__meuLabel->SetText(
__meuEdit->GetText().GetPointer());

__meuLabel->RequestRedraw(true);
}
break;
default:
break;
}
}


A maioria do código já foi implementado pelo plugin, o que fizemos aqui foi definir o texto do label de acordo com o que o usuário digitou na caixa de texto e logo após, solicitar o redesenho do componente. Feito isso, clique com o botão direito sobre o projeto e selecione "Run as > bada Simulator Application". A aplicação deve ficar como na figura abaixo:


Estou começando a aprender essa nova plataforma. Mais informações aqui. O que acharam? Deixem seus comentários.

4br4ç05,
nglauber

domingo, 31 de outubro de 2010

Notificações personalizadas no Android

Olá povo,

A barra de notificações do Android permite chamar a atenção do usuário sem interromper o que ele possa estar fazendo. Por padrão, criamos uma notificação que exibe um texto quando ela é disparada, e outros dois quando a barra de notificações é expandida. Para tal, podemos executar o código abaixo:
// Cria a intent que será excutada ao clicar na notificação
Intent it = new Intent("UMA_ACTIVITY");
PendingIntent pi = 
  PendingIntent.getActivity(context, 0, it, 0);
   
// Criando Notificação
Notification notificacao = new Notification();
notificacao.icon = R.drawable.icon;
notificacao.tickerText = "Chegou uma notificação";
notificacao.when = System.currentTimeMillis();
notificacao.flags = Notification.FLAG_AUTO_CANCEL;

notificacao.setLatestEventInfo(context,
  "Mensagem Superior", "Detalhes", pi);
   
NotificationManager nm = (NotificationManager)
  context.getSystemService(
    Context.NOTIFICATION_SERVICE);
nm.notify(1234, notificacao);

Com isso teremos o resultado abaixo:

Porém, se quisermos personalizar a notificação podemos faze-lo criando um arquivo de layout e definindo-o como view da notificação. Vou exemplificar isso utilizando um exemplo bem comum: barra de progresso. Quando baixamos um arquivo do browser ou do Android market é possível ver a barra de notificação exibindo uma barra de progresso. Segue abaixo o código que faz isso.
Arquivo res/layout/minha_notificao.xml
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout_id"
  android:orientation="horizontal"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:padding="3dp">
 
<ImageView
  android:src="@drawable/icon"
  android:id="@+id/icone"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginRight="10dp"/>

<ProgressBar
  android:id="@+id/barraDeProgresso"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_toRightOf="@+id/icone"
  style="@android:style/Widget.ProgressBar.Horizontal" />
 
<TextView
  android:id="@+id/textoDeProgresso"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_toRightOf="@+id/icone"
  android:layout_below="@+id/barraDeProgresso"/>
</RelativeLayout>

Agora vejamos como criar a notificação personalizada:
Intent it = new Intent("UMA_ACTIVITY");
PendingIntent pi = 
  PendingIntent.getActivity(this, 0, it, 0);
    
final Notification notificacao = new Notification();
notificacao.icon = R.drawable.icon;
// Não usaremos setLatestEventInfo, ao invés
// definimos a pending intent da notificação
notificacao.contentIntent = pi;

// Carrega as views a partir do arquivo de layout
final RemoteViews contentView = new RemoteViews(
  getPackageName(), R.layout.minha_notificacao);

// Define as views remotas como view da notificação
notificacao.contentView = contentView;

// Obtém a referência do NotificationManager
final NotificationManager nm = (NotificationManager)
  getSystemService(Context.NOTIFICATION_SERVICE);

// Dispara a notificação
nm.notify(1234, notificacao); 

// Simulando uma tarefa para atualizar a notificação
new Thread(){
  public void run(){
    for (int i = 0; i <= 100; i+=5) {
      // Simula uma tarefa
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
      }

      // Atualiza as views remotas       
      contentView.setProgressBar(
        R.id.barraDeProgresso, 100, i, false);

      contentView.setTextViewText(
        R.id.textoDeProgresso, i+"% concluído");

      // Reenvia a notificação
      nm.notify(1234, notificacao);
    }
  } 
}.start(); 


A utilização de views remotas (RemoteViews) se dá quando queremos acessar views que pertencem a outro processo. No nosso exemplo estamos acessando views que pertencem a aplicação de Notificação. Para atualizar a view, utilizamos uma thread para simular uma tarefa. Entretanto esse trabalho ficaria a cargo de um Service.

O resultado ficará como abaixo:


[Editado em 11/11/2010]
Uma funcionalidade legal da notificação é a possibilidade de tocar um som e acender o led do telefone.
/* 
É possível passar um som personalizado usando:
notificacao.sound = 
   Uri.parse("file:///sdcard/meu.mp3");

Abaixo usamos o som padrão de notificação do telefone
*/
notificacao.defaults = Notification.DEFAULT_SOUND;

// Ativa a flag para habilitar o led do telefone
notificacao.flags |= Notification.FLAG_SHOW_LIGHTS;

// Define o RGB da cor do led 
// (o hardware deixará aproximado)
notificacao.ledARGB = Color.YELLOW;

// Tempo ligado em milisegundos
notificacao.ledOnMS = 1000;

// Tempo desligado em milisegundos
notificacao.ledOffMS = 1000;

Editado em 31/10/2012
O construtor da classe Notification está deprecated. Devemos usar a classe NotificationCompat.Builder para criar as notificações em versões anteriores a 3.0.
PendingIntent pit =
  PendingIntent.getActivity(
    context, 0, new Intent(), 0);

Notification notificacao = 
  new NotificationCompat.Builder(context)
    .setTicker("Chegou uma mensagem do GCM")
    .setContentTitle("Nova mensagem")
    .setContentIntent(pit)
    .setContentText(extras.getString("mensagem"))
    .setSmallIcon(R.drawable.ic_launcher)
    .setAutoCancel(true)
    .build();

NotificationManager manager = (NotificationManager)
  getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1, notificacao);

Qualquer dúvida, deixem seus comentários,

4br4ç05,
nglauber

quinta-feira, 28 de outubro de 2010

Novos Androids da Motorola: Defy e Spice

Olá povo,

A Motorola reafirmando a cada dia sua paixão pelo Android está lançando mais dois telefones com a plataforma: Spice e Defy.

O Motorola Spice vem com uma tela de 320x240 pixels (similar ao do FlipOut), com teclado slider, câmera de 3 MP, music player que inclui uma biblioteca de músicas, rádio FM, aplicativo de reconhecimento de músicas e Music Store. Ele também conta "com o novo e exclusivo aplicativo Flashback, que permite que o usuário visualize e gerencie suas principais atividades e informações em um único lugar por meio de uma linha do tempo, com o histórico das chamadas, mensagens, eventos, fotos e vídeos do usuário". Deve estar disponível agora em novembro. Mais informações aqui.


O outro lançamento da empresa, o Defy tem uma tela igual ao do Milestone (854x480), porém não conta com teclado físico, o que o torna mais fino e leve. Alguns dos atrativos do aparelho é o suporte a Adobe Flash e sua resistência. Com um design emborrachado, ele "suporta água e poeira, chuvas repentinas, bebida derramada ou até uma queda na areia". Mais informações aqui.

4br4ç05,
nglauber

sexta-feira, 22 de outubro de 2010

Android Live Wallpaper

Olá povo,

Engraçado, esse post aqui eu jurava que já tinha escrito, mas quando pesquisei aqui no blog, vi que tinha ficado só na imaginação :) Então vamos lá. Hoje quero falar um pouquinho de um recurso muito bacana que foi introduzido no Android 2.1: os papéis de parede animados ou simplesmente live wallpapers.

Os live wallpapers são diferentes dos papéis de parede comuns pois são dinâmicos e permitem interação com o usuário. Com isso, ao invés de termos uma imagem parada, eles podem exibir animações e até mesmo mudar de comportamento de acordo com o hora do dia, posição do telefone (baseado no acelerômetro), entre outros.

Temos alguns exemplos bem bacanas que já vêm no próprio Android. O live wallpaper "Água" mostra algumas folhas secas descendo na tela como se estivesse sobre a água, e quando o usuário toca na área de trabalho do telefone faz-se uma onda como se o usuário estivesse tocando sobre água.
Outro exemplo interessante é o "Grama". Ele mostra um gramado balançando com o vento. Com o céu ao fundo, ele muda de cor de acordo com a hora do dia.



Vou apresentar um exemplo simples de um papél de parede animado que mostrará o texto passando da tela e a cor da tela mudando aleatoriamente quando o usuário tocar na tela.
A estrutura do projeto ficará como abaixo:


Vamos começar de baixo pra cima. Vejamos com está o AndroidManifest.xml.


<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="ngvl.android.wallpaper"
android:versionCode="1"
android:versionName="1.0">

<application
android:icon="@drawable/icon"
android:label="@string/app_name">

<service
android:name=".MeuPapelDeParede"
android:permission="android.permission.BIND_WALLPAPER">

<intent-filter>
<action
android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>

<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/meupapeldeparede" />
</service>

<activity
android:name=".MeuPapelDeParedeConfig"
android:exported="true">
</activity>
</application>

<uses-sdk android:minSdkVersion="7" />
<uses-feature
android:name="android.software.live_wallpaper" />
</manifest>

Notem que temos um serviço e uma atividade declaradas no manifest. O serviço é o papel de parede em si. Ele exige a permissão BIND_WALLPAPER para poder defini-lo como wallpaper do telefone. Além disso ele tem a uma ação (action) específica para obedecer à chamadas do S.O. para o pel de parede animado. A tag meta-data referencia o arquivo de configuração do wallpaper, que está em res/xml/meupapeldeparede.xml. Esse arquivo vai definir a tela de configuração do papel de parede que é a atividade declarada no nosso manifest.

<wallpaper
xmlns:android="http://schemas.android.com/apk/res/android"
android:thumbnail="@drawable/icon"
android:settingsActivity="ngvl.android.wallpaper.MeuPapelDeParedeConfig"
/>

Essa tela de configuração permitirá ao usuário configurar o texto que aparecerá no papel de parede e armazenará automaticamente em uma SharedPreference.

<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/app_name"
android:key="meupapeldeparede_config">

<EditTextPreference
android:key="texto_do_papel_de_parede"
android:title="Texto do Papel de Parede" />
</PreferenceScreen>



public class MeuPapelDeParedeConfig
extends PreferenceActivity {

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

getPreferenceManager().setSharedPreferencesName(
MeuPapelDeParede.SHARED_PREFS_NAME);

addPreferencesFromResource(R.xml.tela_de_config);
}
}

Agora vamos ver a classe que faz o trabalho legal :)

public class MeuPapelDeParede
extends WallpaperService {

// Contante da SharedPreference do WallPaper
public static final String SHARED_PREFS_NAME =
"meupapeldeparede_config";

public static final String PREF_KEY =
"texto_do_papel_de_parede";

// Handler que ficará recebendo requisições
// para redesenhar a tela
private final Handler mHandler = new Handler();

// X e Y do texto e a cor de background
private int x = 200, y = 100, bg = 0xF00;

// texto do papel de parede
private String texto;

// Retorna a Engine que pinta a tela e
// trata eventos de toque
// Utilizado pelo framework
public Engine onCreateEngine() {
return new MinhaEngine();
}

// Engine para desenhar o papel de parede
private class MinhaEngine extends Engine implements
SharedPreferences.OnSharedPreferenceChangeListener{

// Pincel pra desenhar na tela
private final Paint mPaint;

// Flag que indica se wallpaper está visível
private boolean mVisible;

public MinhaEngine() {
// Configura o pincel para a cor branca
// e coloca anti-alias pra tirar o "serrilhado"
mPaint = new Paint();
mPaint.setColor(0xffffffff);
mPaint.setAntiAlias(true);

// Se registra para ser notificado
// caso o usuário mudar o texto
SharedPreferences prefs =
MeuPapelDeParede.this.getSharedPreferences(
SHARED_PREFS_NAME, 0);

prefs.registerOnSharedPreferenceChangeListener(
this);

onSharedPreferenceChanged(prefs, null);
}

public void onCreate(SurfaceHolder surfaceHolder){
super.onCreate(surfaceHolder);
// Habilita eventos de touch
setTouchEventsEnabled(true);
}

public void onDestroy() {
super.onDestroy();
// Suspende qualquer "pintura" pendente
mHandler.removeCallbacks(threadPintura);
}

public void onVisibilityChanged(boolean visible){
// Se o display estiver apagado, não fica
// pintando a tela evita desperdiçar
// processamento
mVisible = visible;
if (visible) {
pintaPapelDeParede();
} else {
mHandler.removeCallbacks(threadPintura);
}
}

public void onSurfaceDestroyed(SurfaceHolder sh){
super.onSurfaceDestroyed(holder);
// Ao destruir o "canvas" suspende todas
// as threads pendentes
mVisible = false;
mHandler.removeCallbacks(threadPintura);
}

public void onTouchEvent(MotionEvent event) {
// Captura o X e o Y de onde o usuário
// tocou na tela.
// Gera uma cor de BG aleatóra.
if (event.getAction() ==
MotionEvent.ACTION_DOWN) {

x = (int) event.getX();
y = (int) event.getY();
bg = new Random().nextInt(0xffffff);
}
super.onTouchEvent(event);
}

// Thread que solicita a pintura da tela
private final Runnable threadPintura =
new Runnable() {
public void run() {
pintaPapelDeParede();
}
};

// Método que pinta cada frame da
// animação do papel de parede
void pintaPapelDeParede() {
final SurfaceHolder holder =
getSurfaceHolder();

Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
desentaTexto(c);
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}

// Depois de efetuar a pintura, agenda o handler
// pra pintar o próximo frame.
// Dessa forma, teremos 40 frames por segundo
mHandler.removeCallbacks(threadPintura);
if (mVisible) {
mHandler.postDelayed(threadPintura, 1000/25);
}
}

// Desenha o texto na tela
private void desentaTexto(Canvas c) {
c.save();

c.drawColor(0xff000000 | bg);

float textWidth = 0;
float[] widths = new float[texto.length()];

mPaint.setTextSize(20);
mPaint.getTextWidths(texto, widths);

for (int i = 0; i < widths.length; i++) {
textWidth += widths[i];
}

c.drawText(texto, x, y, mPaint);
x -= 5;
if (x <= -textWidth) {
x = c.getWidth();
}

c.restore();
}

// Se o usuário mudar o texto a ser exibido,
// atualiza o texto do wallpaper
public void onSharedPreferenceChanged(
SharedPreferences prefs, String key) {
texto = prefs.getString(PREF_KEY, "Texto");

}
}
}


Pronto! Agora pra testar, basta ir na tela principal pressionar Menu > Wallpaper > Live wallpapers. Nosso papel de barede deve aparecer na lista. Abaixo temos a figuras do nosso wallpaper em execução.



4br4ç05,
nglauber

quarta-feira, 20 de outubro de 2010

Waldez Luiz Ludwig

Olá povo,

Meu colega Valdécio Borges mandou esse vídeo pelo Buzz. Achei fantástica a análise feita pelo psicólogo e consultor de empresas Waldez Luiz Ludwig no programa "Sem censura". Ele fala com toda sinceridade e clareza como está o mercado de trabalho hoje em dia.

Vale a pena ver esse vídeo e tentar seguir os conselhos ditos.


4br4ç05,
nglauber

quinta-feira, 14 de outubro de 2010

Curso de Android em João Pessoa

Olá povo,

Atendendo a muitos pedidos, a LinuxFI abriu as inscrições para o curso de Google Android em João Pessoa. As aulas acontecerão aos sábados e domingos durante 3 finais de semana.

No curso, estudaremos a revolucionária plataforma para smartphones da Google e como desenvolver aplicativos para ela. Os conceitos de Activities, Handlers, ContentProviders e Services serão aplicados na prática. Vamos ver como armazenar informações no banco de dados do celular com SQLite. Comunicação com WebServices e com o GoogleMaps. Envio de SMS. E muito mais.

Tudo isso podendo ser mostrado em um aparelho real!!!

Quem tiver interesse, pode entrar na página do curso para obter mais informações e fazer sua inscrição.

4br4ç05,
nglauber

quinta-feira, 7 de outubro de 2010

Especialização em TECNOLOGIAS PARA DESENVOLVIMENTO DE APLICAÇÕES MÓVEIS

Olá povo,

O CESAR.edu está lançando o curso de Especialização em Engenharia de Software com ênfase em Tecnologias para Desenvolvimento de Aplicações Móveis (TECDAM) onde o aluno aprenderá a desenvolver aplicações para as plataformas: Android, iOS (iPhone, iPad e iPod), Windows Mobile, Windows Phone 7, BADA, Java ME, Flash Lite, .Net Compact Framework, Appcelerator, Elips, Adobe Air, W3C Widgets, Silverlight e XNA.

O curso será ministrado as sextas (das 18:30 as 22:00) e aos sábados (08:00 as 17:00) nas instalações do CESAR, com aulas práticas e com o apoio de professores que além de experiência em sala de aula, são profissionais especializados nas disciplinas que ministram.

Para obter mais informações clique aqui.

4br4ç05,
nglauber

quarta-feira, 6 de outubro de 2010

Android Widgets 2

Olá povo,

Já coloquei aqui no blog como criar um Widget, mas hoje vou mostrar como tratar eventos em componentes de um Widget.

Crie um novo projeto Android. Vamos começar criando o arquivo de layout do widget. Crie um arquivo /res/layout/widget.xml e deixe-o como abaixo. A imagem que eu estou usando como background eu peguei no próprio diretório do SDK. Procurem por *.9.png lá que vocês vão achar um monte :)


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_frame"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/widget_bg"
>
<Button
android:layout_width="wrap_content"
android:id="@+id/prev" android:text="<<"
android:layout_height="fill_parent"></Button>

<TextView
android:text="@+id/TextView01"
android:id="@+id/TextView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFF"
android:layout_weight="1"
android:gravity="center">
</TextView>

<Button
android:layout_width="wrap_content"
android:id="@+id/next"
android:text=">>"
android:layout_height="fill_parent">
</Button>
</LinearLayout>

Depois, vamos descrever o nosso Widget. Crie o arquivo /res/xml/meuwidget_info.xml. Nesse arquivo, descrevemos o tamanho do widget e o layout que o representa.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="146dp"
android:minHeight="72dp"
android:initialLayout="@layout/widget"
>
</appwidget-provider>

Agora é só criarmos a classe que vai carregar o nosso Widget. Ela herda de AppWidgetProvider, que por sua vez herda de BroadcastReceiver. Logo a maioria do processo de atualização do widget é feito via mensagens de broadcast.

public class MeuWidgetProvider
extends AppWidgetProvider {

private static final String ACTION =
"ngvl.android.meuwidget.WIDGET_CONTROL";
private static final String LOG_TAG = "MeuWidget";
public static final String URI_SCHEME = "meuwidget";

private static int count;

@Override
public void onUpdate(Context context,
AppWidgetManager wm, int[] appWidgetIds) {

Log.d(LOG_TAG, "onUpdate(): ");

int id = appWidgetIds[0];
Log.d(LOG_TAG, "appWidgetId: "+ id);

RemoteViews remoteView = new RemoteViews(
context.getPackageName(), R.layout.widget);

remoteView.setTextViewText(
R.id.TextView01, "texto inicial");

atualizaViews(context, id, remoteView);
}

private void atualizaViews(Context context,
int widgetId, RemoteViews remoteView) {

remoteView.setOnClickPendingIntent(
R.id.next,
getPendingIntent(context, "next", widgetId));

remoteView.setOnClickPendingIntent(
R.id.prev,
getPendingIntent(context, "prev", widgetId));

AppWidgetManager.getInstance(context).
updateAppWidget(widgetId, remoteView);
}

private PendingIntent getPendingIntent(
Context context, String command, int appWidgetId){

Intent active = new Intent();
active.setAction(ACTION);
active.putExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
appWidgetId);

Uri data = Uri.withAppendedPath(
Uri.parse(URI_SCHEME+"://widget/id/#"+command),
String.valueOf(appWidgetId));

active.setData(data);
return PendingIntent.getBroadcast(
context, 0, active, PendingIntent.FLAG_ONE_SHOT);
}

@Override
public void onReceive(Context context, Intent intent) {

final String action = intent.getAction();
Log.d(LOG_TAG, "OnReceive:Action: " + action);
// Bug conhecido do Android 1.5
if (AppWidgetManager.ACTION_APPWIDGET_DELETED.
equals(action)) {

final int appWidgetId =
intent.getExtras().getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);

if (appWidgetId !=
AppWidgetManager.INVALID_APPWIDGET_ID) {

this.onDeleted(context, new int[]{appWidgetId});
}

} else if (ACTION.equals(action)) {
final int appWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);

if (appWidgetId !=
AppWidgetManager.INVALID_APPWIDGET_ID) {

this.trataAcao(context, appWidgetId,
intent.getData());
}

} else {
super.onReceive(context, intent);
}
}

public void trataAcao(Context context,
int appWidgetId, Uri data) {

Log.d(LOG_TAG, "onHandleAction: "+ appWidgetId);
RemoteViews remoteView = new RemoteViews(
context.getPackageName(), R.layout.widget);

Log.d(LOG_TAG, "data = "+ data);
String controlType = data.getFragment();

if (controlType.equalsIgnoreCase("prev")) {
remoteView.setTextViewText(
R.id.TextView01, "Count: "+ --count);

} else if (controlType.equalsIgnoreCase("next")) {
remoteView.setTextViewText(
R.id.TextView01, "Count: "+ ++count);
}
atualizaViews(context, appWidgetId, remoteView);
}
}


O método onUpdate() é chamado quando adicionamos o widget na HomeScreen. Nele estamos obtendo as view remotas. A classe RemoteViews representa views que estão em outro processo, que nesse caso é a aplicação de Home onde ficam os widgets. Então nós obtemos apenas as nossas (baseado no pacote da aplicação e no arquivo de layout). Uma vez com as referencias das views, podemos definir o texto inicial e setar o evento de clique. Esse evento está sendo definido no método atualizaViews().
Os eventos de clique disparam PendingItents para o próprio widget (lembre-se que ele é uma subclasse de BroadcastReceiver). Esses eventos são tratados (como todo receiver) pelo método onReceive() que chama o método trataAcao() para atualizar o texto da view e "reassignar" os eventos aos botões.

Agora declare o widget no AndroidManifest.xml.


<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="ngvl.android.meuwidget"
android:versionCode="1"
android:versionName="1.0">

<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:debuggable="true">

<receiver
android:name=".MeuWidgetProvider">
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<intent-filter>
<action
android:name="ngvl.android.meuwidget.WIDGET_CONTROL" />
<data android:scheme="meuwidget" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/meuwidget_info" />
</receiver>
</application>
</manifest>


Notem que a declaração do Widget é com a tag receiver e no meta-data passamos o arquivo XML com suas configurações. Notem que temos dois intent-filters para o widget: um com a ação android.appwidget.action.APPWIDGET_UPDATE que deve sempre ser declarado, e outro com a ação qeu disparamos no clique do botão.

A estrutura final do projeto ficará dessa forma:



Execute a aplicação, dê um clique longo na HomeScreen e adicione o widget à tela. O resultado é exibido na figura abaixo :)



Estou sem tempo de explicar bem o exemplo, mas tentem colocar pra rodar e qualquer dúvida, postem comentários aqui :)

4br4ç05,
nglauber

domingo, 3 de outubro de 2010

Conversão de código com ANTLR

Olá povo,

Estou no processo final do meu mestrado escrevendo minha dissertação e meu tema aborda a conversão automática de aplicações iPhone para Android. Devo admitir que uma das mais legais foi a conversão do código-fonte de Objective-C (do iPhone) para Java (do Android).
Para realizar essa etapa, utilizei o ANTLR (lê-se Antler), "um a sofisticado gerador de parser que pode ser utilizado para criar interpretadores de linguagens, compiladores e outros tradutores". Ele foi criado e é mantido por Terence Parr.

Essa ferramenta está disponível para download, inclusive com um plugin do Eclipse. O desenvolvedor especifica um arquivo de dicionário, que determina a sintaxe da linguagem especificada. A partir desse arquivo, é gerado o lexer que determina se as sentenças descritas em um arquivo de código-fonte estão em conformidade com a linguagem especificada. Feita essa validação sintática, o lexer passa a árvore com os tokens extraídos do arquivo para o parser. O ANTLR permite que sejam adicionados alguns trechos de código à gramática, tornando o parser gerado pela ferramenta, um tradutor.

Abaixo temos um exemplo da criação de uma linguagem chamada "T". Essa linguagem tem apenas uma regra chamada "r", que pode conter fantásticos DOIS comandos!!! :) Sendo assim, em um arquivo de código-fonte escrito em "T" podemos ter várias (indicada pelo sinal de "+" no arquivo de dicionário) instruções do tipo "print" e/ou "sum". A instrução print recebe um ID que foi definido como sendo qualquer sequência de letras maiúsculas ou minúsculas (número não são aceitos). Já a instrução sum recebe dois INT, que são definidos como qualquer sequência de números.
Notem que as instruções são terminadas com ";" e que temos o código Java para cada instrução, esse código é que será executado quando mandarmos executar um código que esteja de acordo com a linguagem "T".

grammar T;

// Regra r tem os comandos "print" | "sum"
r : ('print' ID ';' {
System.out.println("imprima "+$ID.text);}

| 'sum' id1=INT id2=INT {
int x = Integer.parseInt($id1.text);
int y = Integer.parseInt($id2.text);
int z = x + y;
System.out.println("soma "+ z) ; }
)+
;

ID: ('a'..'z' | 'A'..'Z') + ;

INT: '0'..'9' + ;

// Ignore espaços em branco
WS: (' ' |'\n' |'\r' )+ {$channel=HIDDEN;} ;

Uma vez criado esse dicionário, a ferramenta gera o parser e o lexer em Java. Podemos então finalmente screver nosso primeiro programa em "T" :)

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;

public class AmazingCompiler {
public static void main(String[] args)
throws Exception {

// Código fonte (poderia estar em um arquivo)
String codigoFonte = "print Glauber; sum 2 2;";

// criando stream com o código-fonte
ANTLRStringStream input =
new ANTLRStringStream(codigoFonte);

// Cria uma intância do Lexer que lê as instruções
TLexer lexer = new TLexer(input);

// Cria lista de tokens geradas a partir do lexer
CommonTokenStream tokens =
new CommonTokenStream(lexer);

// Cria o parser pra executar as instruções
TParser parser = new TParser(tokens);

// Manda executar a regra "r".
parser.r();
}
}


O código acima, passa o código-fonte em forma de um stream para o Lexer que o valida e gera os tokens que são repassados para o Parser. Esse último, por sua vez, manda excutar a regra "r", que como já foi dito, pode conter várias instruções "print" e "sum". Uma vez executado esse código, a saída no console será:

imprima Glauber
soma 4

Muito bacana! Agora você pode criar suas liguagens de programação e entrar no hall da fama :)

Tem muita coisa que podemos falar sobre essa fantástica ferramenta. Quem quiser mais informações sobre o ANTLR segue alguns links que foram de grande valia pra mim no meu mestrado.
- Site Oficial (http://www.antlr.org/)
- Vídeos explicativos sobre a ferramenta (http://javadude.com/articles/antlr3xtut/)
- Livro de Referência do ANTLR que é fonte desse artigo.

4br4ç05,
nglauber

sexta-feira, 1 de outubro de 2010

Conheça o C.E.S.A.R.

Olá povo,

Acho que a maioria das pessoas que trabalha com tecnologia aqui no Nordeste já ouviu falar do C.E.S.A.R. (www.cesar.org.br). Mas quem quiser conhecer um pouco mais sobre o instituto que é um dos mais inovadores do Brasil, pode dar uma olhada nesse vídeo institucional.



4br4ç05,
nglauber

quinta-feira, 30 de setembro de 2010

Multi-Touch no Android

Olá povo,

Esse blog sempre é "alimentado" com dúvidas de alunos e colegas meus que eu me atrevo a querer resolver. Hoje pela manhã, chegou um amigo e me perguntou se eu já tinha feito algo com Multi-Touch no Android. Falei que já tinha feito no iPhone, então fui à caça. Achei um ótimo exemplo no site do próprio Android. Quem quiser pode baixar via SVN aqui.
Esse exemplo demonstra a utilização do "efeito pinça" que aumenta o diminui um objeto na tela fazendo movimentos com os dedos similares aos de uma pinça.

O que é preciso é criar um objeto da classe android.view.ScaleGestureDetector passando em seu construtor o contexto (Context) e um objeto que implementa a interface ScaleGestureDetector.OnScaleGestureListener. Esse objeto será notificado quando o "evento de pinça" for disparado.


public class TouchExampleView extends View {
private static final int INVALID_POINTER_ID = -1;

private Drawable mIcon;
private float mPosX;
private float mPosY;

private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;

private ScaleGestureDetector mScaleDetector;

private float mScaleFactor = 1.f;

public TouchExampleView(Context context) {
this(context, null, 0);
}

public TouchExampleView(Context context,
AttributeSet attrs) {

this(context, attrs, 0);
}

public TouchExampleView(Context context,
AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

// A imagem será o ícone da aplicação
mIcon = context.getResources().
getDrawable(R.drawable.icon);
// Define os limites da imagem
mIcon.setBounds(0, 0,
mIcon.getIntrinsicWidth(),
mIcon.getIntrinsicHeight());

// Cria o objeto que capturará o "evento de pinça"
mScaleDetector = new ScaleGestureDetector(
context, new ScaleListener());
}

@Override
// Esse método servirá pra mover a imagem na tela
// e delegar o multi-touch pro ScaleGestureDetector
public boolean onTouchEvent(MotionEvent ev) {

// Delega o evento pro ScaleGestureDetector
// que verificará se o multitouch foi utilizado
mScaleDetector.onTouchEvent(ev);

final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();

// Ao tocar na tela, salva o local tocado
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}

case MotionEvent.ACTION_MOVE: {
final int pointerIndex =
ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);

// Ao mover, checa se o ScaleGestureDetector
// não está processando algum gesto
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;

// Salva o deslocamento feito ao mover
mPosX += dx;
mPosY += dy;

invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}

case MotionEvent.ACTION_UP: {
// nenhum dedo da tela, ponto inválido
mActivePointerId = INVALID_POINTER_ID;
break;
}

case MotionEvent.ACTION_CANCEL: {
// nenhum dedo da tela, ponto inválido
mActivePointerId = INVALID_POINTER_ID;
break;
}

case MotionEvent.ACTION_POINTER_UP: {
// Ao retirar um dos dedos, atualize o ponto
// com o dedo que ficou na tela
final int pointerIndex =
(ev.getAction() &
MotionEvent.ACTION_POINTER_ID_MASK)
>> MotionEvent.ACTION_POINTER_ID_SHIFT;

final int pointerId =
ev.getPointerId(pointerIndex);

if (pointerId == mActivePointerId) {

final int newPointerIndex =
pointerIndex == 0 ? 1 : 0;

mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);

mActivePointerId =
ev.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}

@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);

canvas.save();

// Reposicona a matriz do canvas
canvas.translate(mPosX, mPosY);
// Define a escala (zoom) do canvas
canvas.scale(mScaleFactor, mScaleFactor);
// Redesenha a imagem
mIcon.draw(canvas);

canvas.restore();
}

// classe que ouve o evento de multi-touch
// e calcula a escala
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener{

@Override
public boolean onScale(ScaleGestureDetector d) {
mScaleFactor *= d.getScaleFactor();

// Não permite que o objeto fique
// muito grande nem muito pequeno
mScaleFactor =
Math.max(0.5f, Math.min(mScaleFactor, 5.0f));

invalidate();
return true;
}
}
}

Algumas considerações sobre esse post:
- Para testar esse exemplo você precisará do dispositivo real, afinal você não tem dois cliques de mouse :)
- O suporte a multi touch depende do aparelho;
- API de MultiTouch está disponível apenas para aparelhos com Android 2.0 ou superior.

Abaixo temos a aplicação executando no Motorola FlipOut.



É isso pessoal! Estou com muita coisa pra fazer, estou tentando terminar o mestrado, trabalhando e ainda dando aulas. Quaisquer dúvidas, postem seus comentários abaixo ou consulte o post orginal do blog do android.



Editado pra tentar atender o primeiro comentário desse post.

public class MinhaView extends View {

private Paint bluePaint;
private ArrayList<Point> points;

public MinhaView(Context c) {
this(c, null);
}

public MinhaView(Context c, AttributeSet attrs) {
super(c, attrs);
points = new ArrayList<Point>();

bluePaint = new Paint();
bluePaint.setARGB(255, 0, 0, 255);

setFocusable(true);
}

public boolean onTouchEvent(MotionEvent event){
// Se tocou na tela ou está movendo o dedo
if (event.getAction() ==
MotionEvent.ACTION_DOWN ||
event.getAction() ==
MotionEvent.ACTION_MOVE) {

// Pegue os pontos na tela e alimente a lista
// Normalmente são 1 ou 2 pontos
for(int i=1; i <= event.getPointerCount(); i++){
Point p = new Point(
(int) event.getX(event.getPointerId(i)),
(int) event.getY(event.getPointerId(i)));

points.add(p);
}
// Solicita que a view seja redesenhada
invalidate();
}
return true;
}


public void draw(Canvas canvas) {
super.draw(canvas);

// percorre a lista de pontos
while (points.size() > 0) {
Point p = points.remove(0);
canvas.drawCircle(p.x, p.y, 50, bluePaint);
}
// Limpa a lista pra evitar lixo
points.clear();
}
}


4br4ç05,
nglauber

terça-feira, 28 de setembro de 2010

Curso de Android em Recife

Olá povo,

Hoje começa a quarta turma de Google Android da Especializa Treinamentos. O curso tem carga horária de 40 horas e as aulas serão ministradas por mim, às Terças e Quintas-feiras das 19:00 as 22:00.

No curso, estudaremos a revolucionária plataforma para smartphones da Google e como desenvolver aplicativos para ela. Os conceitos de Activities, Handles, ContentProvider e Services serão aplicados na prática. Vamos ver como armazenar informações no banco de dados do celular com SQLite. Comunicação com WebServices e com o GoogleMaps. Envio de SMS. E muito mais.

Tudo isso podendo ser mostrado em um aparelho real!!!

Quem tiver interesse, pode entrar na página do curso para obter mais informações e fazer sua inscrição. Corram que ainda há tempo!

4br4ç05,
nglauber

segunda-feira, 23 de agosto de 2010

Android: Usando Reflection para acessar recursos

Olá povo,

Alguns alunos já me perguntaram sobre como obter recursos de uma aplicação Android através do nome do arquivo. Isso pode ser feito através da API de Reflection de Java (pacote java.lang.reflect.*). No exemplo abaixo vou mostrar como obter uma imagem a partir do seu nome.


try {
// Obtém a referência da classe R
Class c = Class.forName("nome.do.pacote.da.app.R");

// Obtém as inner classes: id, layout, drawable, ...
Class[] innerClasses = c.getClasses();

// Procurando inner class drawable
Class drawableClass = null;

for (int i = 0; i < innerClasses.length; i++) {
if (innerClasses[i].getSimpleName()
.equals("drawable")){

drawableClass = innerClasses[i];
break;
}
}
// Procurando o atributo com o nome da imagem
Field f = drawableClass.getField("nome_da_img");

// Obtendo o valor do campo
int resourceId = f.getInt(null);

// Criando uma image view apenas pra exemplificar
ImageView img = new ImageView(context);

// Setando a imagem (recurso) do ImageView
img.setImageResource(resourceId);

} catch (Exception e) {
e.printStackTrace();
}


Como já sabemos, a classe R armazena valores do tipo int que representam os recurso da aplicação. O que fizemos aqui foi obter uma referência para a classe R, depois obter uma referência para a classe interna drawable, e por último, pegar o valor de um dos seus atributos baseados no seu nome.
Podemos fazer isso para as demais classes como id, layout, etc.

4br4ç05,
nglauber

sexta-feira, 30 de julho de 2010

Motorola Shop4Apps

Olá povo,

A Motorola acaba de lançar a Shop4Apps, sua própria loja virtual de aplicativos para dispositivos com o sistema operacional Android.

O aplicativo já vem instalado no modelo FlipOut, mas também pode ser acessado de qualquer telefone através do endereço: http://motorola.com/shop4apps.

No Brasil não é possível comprar aplicações no Android Market. O Shop4Apps vem para preencher essa lacuna. A loja também poderá ser utilizada pelos usuários da Argentina e México.

Achei bastante interessante.

4br4ç05,
nglauber

terça-feira, 27 de julho de 2010

Utilização de Estilos no Android - Parte 2

Olá povo,

Dando continuidade ao exemplo de utilização de estilos no Android, vou mostrar como modificar o visual de alguns componentes. Todos os elementos visuais que são selecionáveis ou clicáveis podem utilizar o recurso <selector> que permite modificar a aparência do componente, indicando quais imagens serão utilizadas para cada estado do componente. Essas imagens são definidas em um arquivo XML que deve ficar dentro da pasta res/drawable. Isso mesmo, a pasta drawable não armazena só imagens.
Vamos criar um selector que modificará as imagens de um RadioButton. Para isso, crie o arquivo res/layout/meuradiobutton.xml que deve ficar como abaixo:

<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_window_focused="false"
android:state_checked="false"
android:drawable="@drawable/img_radio_desmarcado" />
<item
android:state_window_focused="true"
android:state_checked="true"
android:drawable="@drawable/img_radio_marcado" />
</selector>

No exemplo acima, dentro da tag <selector> definimos as imagens utilizadas quando o componente estiver marcado (checked) e desmarcado. Lembrando que as imagens "img_radio_marcado" e "img_radio_desmarcado" também devem estar dentro da pasta res/drawable.

Agora devemos aplicar o selector ao RadioButton. Para utilizar o selector, basta utilizar a propriedade android:button.


<RadioButton
android:checked="false"
android:button="@drawable/meuradiobutton"
android:layout_width="wrap_content"
android:layout_heighth="wrap_content"
android:id="@+id/radioButton1"
android:text="Fácil" />


Podemos observar o resultado na figura abaixo:

Um outra característica legal de modificar é a cor de seleção de Grid e Lista. A idéia é a mesma, criamos um selector e o associamos ao GridView ou ao ListView.

<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<!--Imagem do item da lista clicado -->
<item
android:state_pressed="true"
android:drawable="@drawable/img_clicado" />
<!--Imagem do item da lista selecionado sem foco -->
<item
android:state_focused="false"
android:drawable="@drawable/img_selecionado" />
<!--Imagem do item da lista selecionado com foco -->
<item
android:state_window_focused="true"
android:drawable="@drawable/img_selecionado" />
<!--Imagem do item da lista não selecionado -->
<item
android:state_window_focused="false"
android:drawable="@android:color/transparent" />
</selector>


Para esse exemplo temos uma imagem para quando item da lista está selecionado (navegando com o direcional, se o telefone possuir) e outra pra quando o item for clicado. Para aplicar o selector à lista, altere a propriedade listSelector.

<ListView
android:id="@+id/listView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:listSelector="@drawable/meuselector"/>


O resultado é apresentado na figira abaixo.



Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber