2011年8月26日 星期五

想榨乾XOOM的電力嗎?試試看這個會隨時更新Sensor值的Widget

寄件者 scratchlab

Jimmy's papa想試試看在XOOM上,是否能隨時按Home鍵後回到主畫面後,就有個氣壓計小工具(Widget)告訴我目前的氣壓值,這工具可以教Jimmy看著氣壓值並且對照戶外的天氣,去了解兩者之間的關係,而且順便讓Jimmy回想一下上個月到科工館玩氣壓體驗艙當時的感覺

不過Jimmy's papa寫這個小工具時,竟然發覺氣壓值無法更新,一查之下android:updatePeriodMillis這個屬性竟然Android 1.5版後就沒效了(這是網友說的,有待查證),官方文件也提到"Updates requested with updatePeriodMillis will not be delivered more than once every 30 minutes."

解決之道有AlarmManager或TimerTask兩種方式解決,Jimmy's papa覺得TimerTask比較簡單,以下是程式碼

package com.jimmyscratchlab.androidbarometerwidget;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.text.format.DateFormat;
import android.util.Log;
import android.widget.RemoteViews;

public class AndroidBarometerWidget extends AppWidgetProvider {
    private static final String TAG = "sensor";
    private  SensorManager sm;
    private String value = "";
    
    @Override
    public void onUpdate(Context context,AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // TODO Auto-generated method stub
        sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        int sensorType = Sensor.TYPE_PRESSURE;
        sm.registerListener(myPressureListener,sm.getDefaultSensor(sensorType),SensorManager.SENSOR_DELAY_NORMAL);
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new MyTime(context, appWidgetManager), 1, 500);
    }

    
    
     private class MyTime extends TimerTask
     {
         RemoteViews remoteViews;
         AppWidgetManager appWidgetManager;
         ComponentName thisWidget;
         
         public MyTime(Context context, AppWidgetManager appWidgetManager)
          {
             this.appWidgetManager = appWidgetManager;
             remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
             thisWidget = new ComponentName(context, AndroidBarometerWidget.class);
          }
          
         @Override
         public void run()
          {
             remoteViews.setTextViewText(R.id.now,  value);
             appWidgetManager.updateAppWidget(thisWidget, remoteViews);
          }
     }
    
    
    
    
    final SensorEventListener myPressureListener = new SensorEventListener(){

        public void onSensorChanged(SensorEvent sensorEvent){
            
            if(sensorEvent.sensor.getType() == Sensor.TYPE_PRESSURE){
                Log.i(TAG,"onSensorChanged");
                
                java.text.DecimalFormat df = new java.text.DecimalFormat("#.#");

                value = "目前氣壓(PRESSURE): "+ df.format( sensorEvent.values[0] )+"millibars"; 
            }
            
        }

        public void onAccuracyChanged(Sensor sensor , int accuracy){
            Log.i(TAG, "onAccuracyChanged");
        }
    };
     
}

2011年8月23日 星期二

AdMob測試

寫完氣壓計測試程式後,接著Jimmy's papa開始測試如何在APP置入AdMob線上廣告,到Google code下載AdMob的SDK及範例程式,參看相關說明文件後,以下是我的程式範例

package com.jimmyscratchlab.androidbarometer;


import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.google.ads.AdRequest;
import com.google.ads.AdSize;
import com.google.ads.AdView;

public class AndroidBarometerActivity extends Activity  {
    /** Called when the activity is first created. */
    private AdView adView;

    private static final String TAG = "sensor";
    private  SensorManager sm;
    private TextView myTextView;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Create the adView
        adView = new AdView(this, AdSize.BANNER, "你的發佈商ID");

        // Lookup your LinearLayout assuming it’s been given
        // the attribute android:id="@+id/mainLayout"
        LinearLayout layout = (LinearLayout)findViewById(R.id.mainLayout);

        // Add the adView to it
        layout.addView(adView);
        
        // Initiate a generic request to load it with an ad
        AdRequest request = new AdRequest();
        request.setTesting(true);//啟用測試模式,也可以在AdMob管理頁面選擇停用測試模式
        adView.loadAd(request);
        
        //-------------------
        myTextView = (TextView) findViewById(R.id.value);
        sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        int sensorType = Sensor.TYPE_PRESSURE;
        sm.registerListener(myPressureListener,sm.getDefaultSensor(sensorType),SensorManager.SENSOR_DELAY_NORMAL);
        
        

    }
    
    @Override
    public void onDestroy() {
      adView.destroy();
      super.onDestroy();
    }

    
    final SensorEventListener myPressureListener = new SensorEventListener(){

        public void onSensorChanged(SensorEvent sensorEvent){
            
            if(sensorEvent.sensor.getType() == Sensor.TYPE_PRESSURE){
                Log.i(TAG,"onSensorChanged");
                
                java.text.DecimalFormat df = new java.text.DecimalFormat("#.#");
                String reading ="PRESSURE: "+ df.format( sensorEvent.values[0] )+"millibars";
                myTextView.setText(reading);
            }
            
        }

        public void onAccuracyChanged(Sensor sensor , int accuracy){
            Log.i(TAG, "onAccuracyChanged");
        }
    };
     
    public void onPause(){
        //此時需關掉sensor,否則會一直運作,直到電力耗盡
        sm.unregisterListener(myAccelerometerListener);
        super.onPause();
    }
    
}

layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainLayout"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView android:id="@+id/value" android:layout_width="fill_parent"
    android:text="@string/hello" android:textSize="14sp"
    android:layout_margin="30dp" android:layout_height="wrap_content"/>
</LinearLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jimmyscratchlab.androidbarometer" android:versionCode="1"
    android:versionName="1.0">
    <uses-sdk android:minSdkVersion="12" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".AndroidBarometerActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.google.ads.AdActivity"
            android:configChanges="keyboard|keyboardHidden|orientation" />
    </application>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

現在只是測試模式,還不能賺摳摳

寄件者 scratchlab

注意,雖然AdMob有提醒第一次開通需等上兩分鐘,可是Jimmy's papa足足等上一天都盼不到廣告的蹤影,後來只好上AdMob的管理頁面,看是否有哪些選項沒點到,然後點選下圖紅框關於AdSense的選項後,哇!!!好像水庫閘門大開一樣,線上廣告內容源源而至

寄件者 scratchlab

2011年8月19日 星期五

測試XOOM Barometer(氣壓計)

Jimmy's papa一直搞不懂MOTO為何要將Barometer(氣壓計)納入XOOM的Sensor規格中,有網友說爬山用得到,也有人說也許當初採買的物理偵測模組中剛好有Barometer,不過不管真相如何,光看最近新聞報導美國發生怪風吹垮大型演唱會舞台,或者是龍捲風冰雹到處肆虐,或許就美國人而言的確需要身邊的平板電腦或智慧型手機能適時提醒,周遭的氣候是否正在急遽的變化中

以下是簡單的氣壓偵測程式,測試的當下是1008.7 millibar(毫巴),標準大氣壓被定義為1013.25 millibar(毫巴),今天下午雲層還蠻厚的,典型的低氣壓天氣

package com.jimmyscratchlab.androidbarometer;


import com.jimmyscratchlab.androidbarometer.R;
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class AndroidBarometerActivity extends Activity  {
    /** Called when the activity is first created. */
    
    private static final String TAG = "sensor";
    private  SensorManager sm;
    private TextView myTextView;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        myTextView = (TextView) findViewById(R.id.value);
        sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        int sensorType = Sensor.TYPE_PRESSURE;
        sm.registerListener(myPressureListener,sm.getDefaultSensor(sensorType),SensorManager.SENSOR_DELAY_NORMAL);
        
    }
    
    final SensorEventListener myPressureListener = new SensorEventListener(){

        public void onSensorChanged(SensorEvent sensorEvent){
            
            if(sensorEvent.sensor.getType() == Sensor.TYPE_PRESSURE){
                Log.i(TAG,"onSensorChanged");
                
                java.text.DecimalFormat df = new java.text.DecimalFormat("#.#");
                String reading ="PRESSURE: "+ df.format( sensorEvent.values[0] )+"millibars";
                myTextView.setText(reading);
            }
            
        }

        public void onAccuracyChanged(Sensor sensor , int accuracy){
            Log.i(TAG, "onAccuracyChanged");
        }
    };
     
    public void onPause(){
        //此時需關掉sensor,否則會一直運作,直到電力耗盡
        sm.unregisterListener(myAccelerometerListener);
        super.onPause();
    }
    
}

寄件者 scratchlab

我查氣壓計相關資料順便查到一篇有趣的小文章,有個比較KUSO的版本,改天講給Jimmy聽

話說在比奇堡的泡芙阿姨海底駕駛訓練班,在舉行過期末考後,
泡芙阿姨老師打算將其中一名學生海綿寶寶的物理科答案評定為零分,而這名學生海綿寶寶卻堅決認為他應該得到滿分。
兩人互不相讓,沒有結果。
最後雙方達成協議,決定委請另一位公正且是比奇堡當地最有科學頭腦的松鼠珊迪老師來裁決。

  這位被委任的珊迪老師首先要了解一下試卷的內容,
原來,試卷上的的題目是這樣的:「試說明如何利用一具氣壓計測出海之霸餐廳的高度。」
那名學生的答案是:「將此氣壓計攜至海之霸餐廳頂端,繫上一長繩,然後將氣壓計垂放至街道上,再將其收回。
測量所用繩子之長度,則此長度即為海之霸餐廳之高度。」

  看完之後,他覺得這位學生海綿寶寶的回答完整而正確,的確有充足的理由得到滿分。
但這位珊迪老師繼而又想,高分應該代表這名學生海綿寶寶在物理學能力上的一種肯定,
而他的答案卻顯然不能證明如此,於是他建議給這位學生海綿寶寶另一次做答的機會。

  這一次,他有六分鐘的時間回答問題。泡芙阿姨老師並且一再提醒他,答案中必須包含深海物理學方面的知識。
過了五分鐘之後,他的答案仍然一片空白。珊迪老師問他說,是不是準備放棄了。
他抬起頭來回答說:「不,對於這個問題,我有許多不同的答案。我只是在思考一個最佳的答案。」

  在最後一分鐘裡,他一口氣寫完了答案。
他的回答是這樣的:「將氣壓計拿到海之霸餐廳門外,頭頂著氣壓計,吹起大泡泡包覆全身,用馬表計算泡泡到屋頂的時間。
然後利用亞特蘭提斯神奇泡泡浮力公式,即可算出海之霸餐廳的高度。」
泡芙阿姨老師看完答案後一陣苦笑,決定投降。而那位委任的珊迪老師,則給了這位學生海綿寶寶接近滿分的成績。

  在離開辦公室之時,那位受託的珊迪老師突然想起這名學生海綿寶寶曾提到他還有許多不同的答案,
於是又將他找來,問他到底是些什麼答案。
學生回答說:「沒錯。還有許多方法一樣可以測出海之霸餐廳的高度。
舉例來說,你可以在大晴天裡將氣壓計拿到室外,量出氣壓計及其影子的長度,
再量出建築物影子的長度。
利用簡單的比例關係,即可計算出建築物的高度。」

  聽完後,珊迪老師說:「很好,那其它的呢?」於是他又繼續回答說:
「有一個非常基本的計算法,我想你會喜歡的。只要拿著氣壓計沿著階梯而上,
一面沿著牆壁用氣壓計的長度為單位,畫下記號。
最後,計算一下這些記號的數目,即可得到海之霸餐廳的高度,這是一個非常直接的方法。
當然啦,如果你希望用一個較複雜的方法,可以將氣壓計繫於繩子的末端,當作鐘擺一般擺動。
然後分別在街道,以及樓頂測出其重力加速度,利用此差異即可求出海之霸餐廳的高度。」

  最後海綿寶寶下結論說,還有其它的方法可以解決此一問題。
也許最好的方法就是直接帶著氣壓計去敲海之霸餐廳的門。
當皮老闆出來應門時,對他說:「皮老闆先生,我這兒有個很棒的氣壓計,
如果你願意告訴我海之霸餐廳的高度,我就把氣壓計加上蟹堡秘方一併送給你。」

  這時候,珊迪老師忍不住問他說:「你難道真的不知道這個問題的正確解答嗎?」
他承認說知道,但他接著說:「從小到大,我已經厭倦老師不斷教導我們如何去思考,
如何去使用『科學的方法』,而不是教導我們去認識事物的構造與本質。
因此,我才以這種玩笑的方式來表達我的抗議。」

2011年8月16日 星期二

Google宣布以天價125億美元併購MOTO行動通訊部門

Google重金買下MOTO這帖大補丸,這是今天Android陣營的大消息,對於Android APP開發者來說,這無疑是顆定心丸,暫時不用再擔心Android會被其他陣營以專利權手段打趴在地

呵呵,這是否代表Jimmy's papa的XOOM未來更新Android版本速度會加快一點嗎?

2011年8月12日 星期五

用Android程式列出我的XOOM的感應器

Jimmy's papa最近查一下MOTO XOOM官網的規格說明,有關Sensor的部份官網提到有

Proximity(近距離), 
ambient light(環境光), 
barometer(氣壓計), 
gyroscope(陀螺儀)

不過好像跟在其他的平板討論區提到的有點不大一樣,印象中有加速感應器(acceleration)卻沒有距離感應器(Proximity)

為求真相,Jimmy's papa只好捲起袖子動手寫程式囉!!!最後程式跑出的詳細感應器列表有一堆

KXTF9 3-axis Accelerometer,
Ambient Light sensor,
AK8975 3-axis Magnetic field sensor, 
AK8975 Orientation sensor, 
BMP085 Pressure sensor(這應該是氣壓計), 
L3G4200D Gyroscope sensor, 
Gravity Sensor, 
Linear Acceleration Sensor, 
Rotation Vector Sensor
寄件者 scratchlab
package com.jimmyscratchlab.androidsensorlist;

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

import android.app.ListActivity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.os.Bundle;

public class AndroidSensorListActivity extends ListActivity {
   /** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       SensorManager sensorManager
            = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
       List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);

       List<String> sensorTypeList = new ArrayList<String>();
       for(int i=0; i<sensorList.size(); i++){
           sensorTypeList.add(sensorList.get(i).getName());
       }
      
       setListAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,
               sensorTypeList)); //Jimmy's papa查過了,simple_list_item_1是內建的
       //getListView().setTextFilterEnabled(true);
       Log.i("SensorTypeList", sensorTypeList.toString());
   }
}

2011年8月10日 星期三

多個Activity之間藉著Boundle物件傳值

Jimmy's papa覺得Android系統很像個後台有各種專職人員可提供使用者包山包海的服務,但前台的服務櫃台卻空間狹小的一次只能容身一個服務員面對使用者,為了確保服務品質不跳針,當各種專職Activity物件輪流上台時,就得藉由Boundle物件紀錄各項交辦事項

以下是Jimmy's papa將如何以Boundle物件在多個Activity之間傳值的寫法做個小整理

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
      package="com.jimmyscratchlab.蟹堡王"  
      android:versionCode="1"  
      android:versionName="1.0">  
    <application android:icon="@drawable/icon" android:label="@string/app_name">  
        <activity android:name=".收銀員章魚哥"  
                  android:label="@string/app_name">  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  
                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  
          
        <activity android:name=".神廚海綿寶寶"></activity>  
    </application>  
    <uses-sdk android:minSdkVersion="3" />  
</manifest>   
package com.jimmyscratchlab.蟹堡王;  
  
import android.app.Activity;  
import android.content.Intent;  
import android.os.Bundle;  
import android.view.MotionEvent;  
   
public class 收銀員章魚哥 extends Activity {  //Squidward
    
    private int requestCode = 110;
    
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
    }  
      
    public boolean onTouchEvent(MotionEvent event) {  
        Intent intent = new Intent();  
        intent.setClass(收銀員章魚哥.this, 神廚海綿寶寶.class);  
        Bundle mBundle = new Bundle();  
        mBundle.putInt("Petty", 5);//寫入蟹堡數量
        intent.putExtras(mBundle);  
    startActivityForResult(intent, requestCode);
        finish();  
        return super.onTouchEvent(event);  
    }
  
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // 確認回傳的requestCode和resultCode
    if (requestCode == this.requestCode) {
        if (resultCode == RESULT_OK) {
            //do something..........        
        }
    }

    }    
}  

package com.jimmyscratchlab.蟹堡王;   
  
import android.app.Activity;  
import android.os.Bundle;  
  
public class 神廚海綿寶寶 extends Activity{  //SpongeBob
      
    public void onCreate(Bundle savedInstanceState) {  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.main);  
       Bundle bundle = getIntent().getExtras();    
       Int petty=bundle.getInt("Petty");//讀出蟹堡數量  
       setTitle("感謝您!!!我親愛的的客人,我小神廚馬上現場製作"+petty+"個油滋滋的美味蟹堡"!!!);
       bundle.putString("toSquidward", "章魚哥,記得給客人一個笑臉,這樣客人才有美好的一天呦!!!");
       intent.putExtras(bundle);
       setResult(RESULT_OK, intent);
       finish();
    }  
}  

2011年8月3日 星期三

將XOOM內存的檔案系統掛載(mount)到Ubuntu

雖然之前Jimmy's papa已經設定好讓Ubuntu11.04認得MOTO XOOM的USB序號,但是一直無法將XOOM內存的檔案系統掛載(mount)到Ubuntu,只好借助adb或Eclipse's DDMS推拉檔案,剛好最近查到XDA論壇有篇文章教人如何Mount XOOM internal storage in Ubuntu,Jimmy's papa趁空測了一下

首先,安裝處理MTP (media transfer protocol)設備的套件

sudo apt-get install mtpfs
然後編輯這個規則檔
sudo gedit /etc/udev/rules.d/51-android.rules
加入這兩行
SUBSYSTEM=="usb", ATTR{idVendor}=="22b8", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666"
新增目錄,並指定目錄擁有者(假設指定給jimmypapa這個帳號)
sudo mkdir /media/xoom
sudo chown jimmypapa:jimmypapa /media/xoom
然後編輯這個檔案
sudo gedit /etc/fstab
加入這兩行,可加入XOOM載入點到fstab
# mount point for moto xoom
mtpfs     /media/xoom     fuse     user,noauto,allow_other      0      0
編輯fuse.conf
sudo gedit /etc/fuse.conf
將此行開頭的#去掉
#user_allow_other
然後編輯此檔
sudo gedit /etc/group
搜尋有fuse關鍵字的那行,將你的使用者帳號名稱加入行尾,不過Jimmy's papa發覺帳號早已加入
fuse
最後將Ubuntu重開機後,就會發現點選位置選單後,xoom會出現在選單內,不過此時還不能看到XOOM的檔案目錄,必須將usb連接線插上XOOM及PC後才能存取檔案