2010年12月24日 星期五

驗證 GTalk 使用明文/加密文傳遞聊天內容

我一直以為 GTalk 是加密傳送聊天訊息的,直到今早一位在火燙公司的學弟,請我跟他用 GTalk 聊天時,意外發現 GTalk 原來不是完全的加密聊天的。


情境敘述:


目前共有三台電腦



  1. 第一台是 Ubuntu 10.04 安裝 Pidgin 2.6.6 版本,上頭標示支援 GTalk SSL/TLS 加密傳輸,使用 A 帳號

  2. 第二台是 Windows XP,安裝 GTalk 官方繁體中文版 1.0.0.105,此版本不提供加密服務,使用 B 帳號

  3. 第三台是 Windows 7,安裝 GTalk 官方英文版 1.0.0.104,此版本提供加密服務,使用 C 帳號


測試結果:



  • 當 A 跟 B 進行聊天時,發現兩者傳遞的內容都是明文的 XML 資料,其中 <body> 內記錄的就是聊天內容,除此之外,當然連帳號資訊都是未加密的

  • 當 A 跟 C 進行聊天時,此時兩端會使用加密傳遞

  • 當 B 跟 C 進行聊天時,此時兩端皆會使用不加密傳遞,結果跟 A 與 B 一樣


因此驗證了一件事,不要以為自己的 IM client 標榜加密,就等於跟人溝通都會加密的!還是會看對方情況來處理,例如 B 跟 C 都已是官方的軟體,只是一個是繁體版,另一個是英文版,但繁體版不加密,導致英文版最後也配合繁體版進行不加密的通訊。


查了一些文章,這篇說的跟我測的差不多:正面全裸的Gtalk 1.0.0.105,慎用!


所謂的加密部分,只能確保 gtalk client 端連到 google server 端的過程,但從 google server 連到對方那端時,不見得會加密(例如對方透過 http 在 GMail 上用)。除此之外,這次測到的一個大問題是:


就算你自己的 gtalk client 可以提供加密連線,但對方不提供時,結果自己連到 google server 這端也就不走加密


呼,還是萬試謹言慎行啊!還有,快去用英文版就好!


最後附上一些圖示:


gtalk 加密
GTalk 使用加密時的封包訊息


gtalk未加密
GTalk不使用加密時的封包,其中 <body> 儲存的就是聊天訊息,此例為 "Hello" 訊息,除此之外連同通訊雙方的帳密資訊也是曝光的,並使用可讀性佳的 XML 描述


2010年12月18日 星期六

Windows 7 常用免費軟體

回老家,順便重灌一下電腦,神清氣爽啦,順便記一下最近常用的免費軟體:



工作類:



Firefox Plugin:



Google Chrome Plugin:



筆記:


XMarks 可以把書籤資料紀錄在 Server 上,該 Server 可以是自己架的,也可以直接用 XMarks 上面的。而多個瀏覽器的好處,我可以把工作上的同步在 Google Chrome 上頭,然後把家裡用的同步在 Firefox 上頭,如此一來也可以分得很乾淨,另一種用法也可以用目錄分辨工作或家用。另外,我在 Firefox 上僅使用書籤工具列,好處是看到不錯的網頁就直接拖拉分頁進去記錄,而書籤工作列上只要目錄分的好,就不會有顯示上的問題,只會有操作上的便利。


2010年12月16日 星期四

Android 開發筆記 - 簡易的 Widget 實作

MyWidgetShow


除了要從選單點選來執行的應用程式外,還有另一種呈現的方式,那就 Widget 模式,也就是在廣告單上常常看到簡介某大廠 Android 手機時,其手機畫面正顯示的天氣資訊,有點像似待機時顯示在背景的資訊。這種程式實作上並沒有特別困難,但是我看了一些文章教學,反而很著重在介面設計,畢竟 Widget 的特色就是要美美的,結果對我這種介面沒經驗的人來說,往往第一步就卡住了,像是要用內建的 draw9patch 去弄圖片等,故在此就先完全不理會,連什麼背景框框都不要,純粹用來筆記實作 Widget 的流程。


實做 Widget 跟一般 project 的設定沒有差太多,但還是要多留意:



  1. 建立專案時,不見得要使用 Activity

  2. 編寫 AndroidManifest.xml 檔案

  3. 新增描述 Widget 的檔案

  4. 設定 Widget 排版

  5. 實作更新 Widget 的物件


建立一個 project


[Eclipse]->[File]->[New]->[Android Project]

Project name: MyWidget
Build Target: Android 2.2
Application name: MyWidget
Package name: com.test.widget
Min SDK Version: 8


留意的是在此不啟用 Create Activity,這並不是必須的選項。啟用 Activity 的好處,可以規劃使用者在新增 Widget 時,可以在過程中可以多加設定動作。在此僅學習建立 Widget 的流程,就不多提了。


MyWidgetStru


設定 AndroidManifest.xml 檔案


新增 <receiver> 等敘述標籤,別於以前的 <activity>。

<receiver android:name=".MyWidget" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
        android:resource="@xml/widget" />
</receiver>


在此 reciever 的 name 代表之後會建立的 Class 名稱,也就用更新 Widget 資訊的。而 meta-data 中所描述的 resource 則是下一步要新增 Widget 的描述檔


完整描述:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.widget"
    android:versionCode="1"
    android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <receiver android:name=".MyWidget" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/widget" />
        </receiver>

    </application>
    <uses-sdk android:minSdkVersion="8" />
</manifest>


建立 xml/widget.xml 檔案,用以描述 Widget 的資訊


在 res 目錄裡建立新的目錄,名為 xml,接著在 res/xml 目錄裡建立一個檔案,可從 xml 按右鍵->[News]->[Other]->[Android]->[Android XML File],並填寫 File 為 widget.xml 並勾選 AppWidget Provider 的型態,最後就可以按 Finish 結束,接著開啟 xml/widget.xml 檔案進行編輯


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

>
</appwidget-provider>


在此 minWidth 和 minHeight 是設定 Widget 顯示的範圍,而 dp 的數值計算可參考官網的教學,在此不多談,而 updatePeriodMillis 代表此 Widget 更新的頻率,1000 代表 1 秒,所以 1800000 代表 30 分鐘更新一次,更新頻率不宜過高,容易使得手機沒電。然而,經測試發現,目前內定的情境,無論把更新頻率弄到多快,最少要等 30 分鐘才會更新一次,解決的方式也不是沒有,在此先不多談。


設定排版部份


在此使用預設的 main.xml 檔案,開啟 main.xml 檔案後,替已存在的 TextView 加上 android:id="@+id/now"、android:gravity="center"、android:textColor="@android:color/black" 和 android:textSize="18sp" ,另外,再把 layout_height 更新為 fill_parent 即可。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:id="@+id/now"
    android:gravity="center"
    android:textColor="@android:color/black"
    android:textSize="18sp"
    android:layout_width="fill_parent"

    android:layout_height="fill_parent"
    android:text="@string/hello"
    />
</LinearLayout>


實做 Widget 的資訊更新


在 src/com.test.widget 裡,新增一個 Class 名為 MyWidget.java


package com.test.widget;

import java.text.SimpleDateFormat;
import java.util.Date;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

public class MyWidget extends AppWidgetProvider {
    @Override
    public void onUpdate(Context context,AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        RemoteViews updateViews = new RemoteViews( context.getPackageName(), R.layout.main);
        updateViews.setTextViewText(R.id.now,  new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss" ).format( new Date() ) );
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
    }
}


最後,把程式 Run 起來,起初模擬器上不會顯示任何效果,因為 Widget 是要讓使用者自行設置的,需要透過模擬器鍵盤上的 [Menu]->[Add]->[Widgets] 就可以找到 MyWidget,點選下去才是真的安裝此 Widget 啦,成果就是螢幕上面多了一個時間囉!這也是 MyWidget.java 裡頭寫得東作,僅把 TextView 上的內容更新為時間。另外,若想要把已安裝的 Widget 刪除,僅需用滑鼠按住它,接著 Home 就會變成垃圾桶,拖進去就可以囉。


2010年12月15日 星期三

Android 開發教學筆記 - 使用 Regular Expression、Network Connection 和 Thread

getNews


打算寫一個稍微複雜的小程式,練習的項目:



  • 使用 Regular Expression

  • 使用 Network Connection

  • 使用 Thread


以三個項目為出發點,把以前的老題目拿出來:定期抓台灣 Yahoo 首頁的兩則焦點新聞,顯示在 Android 模擬上。因此,先想一下排版問題,大概就三個 TextView,分別為 更新時間 和 兩則新聞。


建立 project


[Eclipse]->[File]->[New]->[Android Project]

Project name: MyWeb
Build Target: Android 2.2
Application name: MyWeb
Package name: com.test.Web
Create Activity: MyWeb
Min SDK Version: 8


設定使用網路權限


點選 AndroidManifest.xml 檔案,在 <manifest> 裡增加 <uses-permission android:name="android.permission.INTERNET" />


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test.web"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MyWeb"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>


設定排版


點選 main.xml 檔案,增加 3 個 TextView,分別為"更新時間"、"新聞1"和"新聞2"。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
<TextView android:id="@+id/UpdateDate" android:layout_width="fill_parent" android:layout_height="wrap_parent"></TextView>    
<TextView android:id="@+id/News1" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/News2" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>

</LinearLayout>


MyWeb.java


package com.test.web;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import com.test.web.GetYahooNews;

public class MyWeb extends Activity {
    Handler jobs;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        jobs = new Handler();
        new Thread( new GetYahooNews( this )).start();
    }

    public void updateNews( final ArrayList<String> news )
    {
        jobs.post( new Runnable(){
            public void run()
            {
                TextView showTextView;

                if( news != null && news.size() >= 2 )
                {
                    if( ( showTextView = (TextView) findViewById(R.id.News1) ) != null )
                        showTextView.setText("[News] "+(String)news.get(0));
                    if( news.size() == 4 && ( showTextView = (TextView) findViewById(R.id.News2) ) != null )
                        showTextView.setText("[News] "+(String)news.get(2));
                }
                if( ( showTextView = (TextView) findViewById(R.id.UpdateDate) ) != null )
                {
                    DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    showTextView.setText( "[Update @ " + dateFormat.format(new Date() ) + "]" );
                }
            }
        });
    }
}


GetYahooNews.java


package com.test.web;

import java.io.*;
import java.util.ArrayList;
import java.util.regex.*;
import java.net.HttpURLConnection;
import java.net.URL;

import android.util.Log;

import com.test.web.MyWeb;

public class GetYahooNews implements Runnable {
    MyWeb activity;
    
    GetYahooNews( MyWeb n)
    {
        this.activity = n;
    }
    public static void report( String message )
    {
        //System.out.println( "[Report] " + message );
        Log.d( "Report" , message );
    }
    public static ArrayList<String> getHotNews()
    {
        ArrayList<String> news = new ArrayList<String>();
        HttpURLConnection con = null;
        try
        {
//*
            URL url = new URL("http://tw.yahoo.com");
            con = (HttpURLConnection) url.openConnection();            
            con.setReadTimeout(10000);
            con.setConnectTimeout(15000);
            con.setRequestMethod("GET" );
            con.addRequestProperty("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9");
            con.setDoInput(true);
            con.connect();

            BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8" ));
// */
//            BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream("fetch.html"), "UTF-8" ));

            String n, result="";
//            while( ( n = reader.readLine() ) != null )
//                result += n;

            StringBuilder htmlContent = new StringBuilder();
            while ((n = reader.readLine()) != null)
               htmlContent.append(n);

            result = htmlContent.toString();

//            BufferedWriter out = new BufferedWriter( new FileWriter("fetch.html") );out.write(result);out.close();
            
            String pattern;
            int at;

            pattern = "<label class=\"img-border clearfix\">";
            if( ( at = result.indexOf(pattern) ) < 0 )
            {
                news.add( "format error 1" );
                report( "format error 1" );
                return news;
            }
            result = result.substring( at );

            pattern = "<ol class=\"newsad clearfix\">";
            if( ( at = result.indexOf(pattern) ) < 0 )
            {
                news.add( "format error 2" );
                report( "format error 2" );
                return news;
            }
            result = result.substring( 0 , at  );

            pattern = "<h3[^>]*>[^<]*<a href=\"(.*?)\"[^>]*>(.*?)</a></h3>";
            Pattern p = Pattern.compile( pattern , Pattern.CASE_INSENSITIVE | Pattern.DOTALL );
            Matcher m = p.matcher( result );

            while( m.find() )
            {
//                report( "\n==== Get === \n" + m.group() + "\n" );
//                report( "URL: " + m.group(1) );
//                report( "Title: " + m.group(2) );
                String newsTitle = ""+ m.group(2);
                String newsUrl = "" + m.group(1);
                if( ( at = newsUrl.indexOf( "http:" ) ) > 0 )
                    newsUrl = newsUrl.substring( at );
                news.add( newsTitle );
                news.add( newsUrl );
            }
        }catch(Exception e)
        {
            news.add( "Exception:"+e );
        }
        finally
        {
            if ( con != null )
                con.disconnect();
        }
        return news;
    }
    public void run()
    {
        try
        {
            while( true )
            {
                try
                {
//                    ArrayList<String> demo = new ArrayList<String>();demo.add("Yo1");demo.add("Yo2");demo.add("Yo3");demo.add("Yo4");activity.updateNews( demo );
                    activity.updateNews( getHotNews() );
                    Thread.sleep( 1000 * 60 * 60 * 2  );    // 2 hrs
                }
                catch(Exception e)
                {
                    report( "run while Exception:"+e );
                    break;
                }
            }
        }
        catch( Exception e )
        {
            report( "run Exception:"+e );
        }
    }
}


在 MyWeb 裡有一樣東西比較特別,叫做 Handler,可以把他當作管理 Thread 的用法,在官網的描述裡,比較重要的敘述:


There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.


在這邊使用 Handler,是讓更新 UI 的部份能夠透過 Runnable 來處理事件,如果不使用的話,會有無法更新 UI 的現象,暫時還沒了解底層的問題。


接著是 MyWeb 裡的 updateNews 函數,其參數是使用 final 的描述,加上這個描述後,可以讓接下來的 new Runnalbe 裡,可以直接用傳進來的參數,如果不用 final 也有解法啦,就是自己在定義一個實做 Runnable 的物件,然後把參數都傳遞好,稍微麻煩一點:


public void updateNewsPrev( ArrayList<String> news )
{
    class UIUpdate implements Runnable
    {
        ArrayList<String> news;
        UIUpdate( ArrayList<String> in )
        {
            news = in;
        }
        public void run()
        {
            TextView showTextView;
            if( news != null && news.size() >= 2 )
            {
                if( ( showTextView = (TextView) findViewById(R.id.News1) ) != null )
                    showTextView.setText("[News] "+(String)news.get(0));
                if( news.size() == 4 && ( showTextView = (TextView) findViewById(R.id.News2) ) != null )
                    showTextView.setText("[News] "+(String)news.get(2));
            }
            if( ( showTextView = (TextView) findViewById(R.id.UpdateDate) ) != null )
            {
                DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                showTextView.setText( "[Update @ " + dateFormat.format(new Date() ) + "]" );
            }
        }
    }
    jobs.post( new UIUpdate( news ) );
}


接下來是 GetYahooNews 的部份,其中 getHotNews 函數是擷取台灣 Yahoo 首頁的兩則新聞出來,以此當作資料,接著就只是單純的當個 Runnalbe 在跑,並且設定 2 小時跑一次,跑完一次會呼叫 MyWeb 的 updateNews 去更新 UI 部份。至於怎樣從 GetYahooNews 呼叫 MyWeb 呢?那就是一開始在使用 GetYahooNews 時,就把 MyWeb 傳進去給他記住,在此使用 activity 變數紀錄。


Android 開發教學筆記 - 使用 String 和 StringBuilder 的差別

在練習使用 HttpURLConnection 來取得指定 URL 網址內容時,發現透過 Android 模擬器跑得非常非常緩慢,但我把相同的 Java Code 用桌機的環境執行,卻十分快速,讓我不禁感到奇怪,難道這是模擬器的問題嗎?還是初始化網路連線所耗的資源問題呢?


後來看到一則文章:Re: [android-developers] Re: First HTTP/S requests are slow. [Android 1.5] - msg#03831,看到了解法!


原來我寫得程式碼:


String n, result="";
while( ( n = reader.readLine() ) != null )
    result += n;


文章提到的寫法:


String n, result="";
StringBuilder htmlContent = new StringBuilder();
while ((n = reader.readLine()) != null)
    htmlContent.append(n);
result = htmlContent.toString();


這兩者的速度至少差 20 倍啊!讓我寫得程式從原先要等約 130 秒,瞬間變成只要等五秒,真是太神奇了。我的 CPU 是 AMD X4 945 + 4GB 記憶體,也不見得會跑很慢才是,因此找了不少文章,才發現是寫法與模擬器的影響吧。程式還包括網路連線,變數很多,很難找出此點。


完整的程式碼:


HttpURLConnection con = null;
try
{
    URL url = new URL("http://tw.yahoo.com");
    con = (HttpURLConnection) url.openConnection();            
    con.setReadTimeout(10000);
    con.setConnectTimeout(15000);
    con.setRequestMethod("GET" );
    con.addRequestProperty("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9");
    con.setDoInput(true);
    con.connect();

    BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8" ));

    String n, result="";

//    while( ( n = reader.readLine() ) != null )
//        result += n;

    StringBuilder htmlContent = new StringBuilder();
    while ((n = reader.readLine()) != null)
       htmlContent.append(n);
    result = htmlContent.toString();
}
}catch(Exception e)
{
}
finally
{
    if ( con != null )
        con.disconnect();
}