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