2012年11月14日水曜日

Android-4.2へバージョンアップ




Galaxy NexusおよびNexus7のOTAが開始しました。
と、いうことで早速、バージョンアップを行い動作確認を行いました。


ロック画面用Widget

新機能です。ロック画面にて左swapするとロック画面用Widgetの設定画面が現れます。




十字ボタンをクリックするとWidget選択画面が表示されます。
その際のLogはこちら。


11-13 23:33:22.010: I/ActivityManager(389): START u0 {act=android.appwidget.action.KEYGUARD_APPWIDGET_PICK flg=0x34800000 cmp=com.android.settings/.KeyguardAppWidgetPickActivity (has extras)} from pid 389
11-13 23:33:22.088: I/AppWidgetAdapter(1824): EXTRA_CUSTOM_INFO not present.
11-13 23:33:22.252: I/ActivityManager(389): Displayed com.android.settings/.KeyguardAppWidgetPickActivity: +178ms
11-13 23:33:22.260: D/YouTube(3693): youtube.app.prefetch.PrefetchStore$UserPresentReceiver.onReceive:364 Received: android.intent.action.USER_PRESENT
11-13 23:33:22.260: D/PhoneStatusBar(557): disable: < expand icons alerts ticker system_info BACK HOME recent* clock search >
11-13 23:33:22.268: D/NfcService(639): NFC-C ON
11-13 23:33:22.346: D/PhoneStatusBar(557): disable: < expand icons alerts ticker system_info back* home* recent clock search >


以下のキーワードがポイントとなるでしょう。ソースが公開されたらgrepしてみよう!
 android.appwidget.action.KEYGUARD_APPWIDGET_PICK
 KeyguardAppWidgetPickActivity



DayDream

Android公式サイトに"DayDream"なる記載があります。
 http://www.android.com/whatsnew/

この機能は、Dock装着時または充電時にスクリーンセーバーを表示させます。
設定→ディスプレイ→スクリーンセーバーから、以下の設定が可能。

  • 時計
  • カラー
  • カレント
  • フォトテーブル
  • フォトフレーム


試しにフォトテーブルを設定して、"DayDream"を起動してみました。
その際のLogはこちら。


11-14 00:06:14.061: I/PowerManagerService(389): Nap time...
11-14 00:06:14.061: I/DreamManagerService(389): Entering dreamland.
11-14 00:06:14.068: I/DreamController(389): Starting dream: name=ComponentInfo{com.android.dreams.phototable/com.android.dreams.phototable.PhotoTableDream}, isTest=false, userId=0

DreamManagerServiceというServiceが新設されているようです。

また、OTAのzipファイルの中身を覗いてみると、PhotoTable.apkというアプリが存在していました。


(つづく)

2012年10月10日水曜日

Nexus 7をandroid-4.1.2へバージョンアップ

すでにNexus 7向けのandroid-4.1.2 OTAが開始されていました。
XDAに投稿されていました。
http://forum.xda-developers.com/showthread.php?p=32587742


OTAの適用方法
rootを取っていなくてもOKです。

  1. adb reboot bootloaderを実行
  2. Recovery Mode起動
  3. apply update from ADBを選択
  4. adb sideload "OTAのfilename"を実行




標準ランチャーの横画面
いままで頑なに縦画面しか表示されなかった標準ランチャーですが、ついに横画面に対応しました。
これは便利!

2012年8月30日木曜日

Android 4.1 JellyBeanのロック画面表示中のActivityLifeCycleについて

Androidのバージョンによって、動作に差異があったのでメモ。
おそらく、Google側も省電力を気にしているのだと思います。




Android 2.3.3 GB(API Level 10) のActivityLifeCycle

電源キー押下によりScreen OFF
onPause()

電源キー押下によりScreen ON → ロック画面表示中
onResume()

ロック画面解除
※ActivityのActivityLifeCycleメソッドはコールされず

Android 4.0.X ICS(API Level 14,15) のActivityLifeCycle

電源キー押下によりScreen OFF
onPause()
 ↓
onStop()

電源キー押下によりScreen ON → ロック画面表示中
onRestart()
 ↓
onStart()
 ↓
onResume()

ロック画面解除
※ActivityのActivityLifeCycleメソッドはコールされず

Android 4.1 JellyBean(API Level 16) のActivityLifeCycle

電源キー押下によりScreen OFF
onPause()
 ↓
onStop()

電源キー押下によりScreen ON → ロック画面表示中
※ActivityのActivityLifeCycleメソッドはコールされず
ロック画面解除
onRestart()
 ↓
onStart()
 ↓
onResume()

2012年8月28日火曜日

自アプリケーションがTOPかどうか確認する方法(4)

ActivityのTaskListの順番を見るよりは、アプリケーションプロセスを見る方が正確だと思います。
ActivityManager#getRunningAppProcesses()で取得可能です。


処理:

  1. ActivityManagerから実行中プロセス情報一覧を取得
  2. 自アプリケーションが存在するかチェック
  3. プロセス情報(importance )の状態を確認する


ソースはこんな感じ。


    private void checkRunningAppProcess(){
        Log.e("","checkRunningAppProcess");

        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        List<runningappprocessinfo> processInfoList = am.getRunningAppProcesses();
        for( RunningAppProcessInfo info : processInfoList){
            if(info.processName.equals(getPackageName())){
                if( info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
                    // app is FOREGROUND
                    Log.e("","app is FOREGROUND");
                }
            }
        }
    }

2012年8月27日月曜日

自アプリケーションがTOPかどうか確認する方法(3)

ActivityManager#getRecentTasks()の情報って?

getRunningTasks()と同様にgetRecentTasks()を見てみます。

ActivityManagerService#getRecentTasks()の処理をチェック。

    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
            int flags) {
                     :
                     :
            final int N = mRecentTasks.size();
            ArrayList<ActivityManager.RecentTaskInfo> res
                    = new ArrayList<ActivityManager.RecentTaskInfo>(
                            maxNum < N ? maxNum : N);
            for (int i=0; i<N && maxNum > 0; i++) {
                     :
                     :
       

mRecentTasksのTaskRecord情報からListを生成します。

TaskRecordをmRecentTasksに追加するタイミング

ActivityManagerService#addRecentTaskLocked(TaskRecord task)でaddを行っており、
このモジュールをコールするのはActivityStackのこちら。

final boolean realStartActivityLocked()
final boolean resumeTopActivityLocked()

Activity起動、またはresumeを行ってActivityがTopになったタイミングでtaskをaddします。


TaskRecordをmRecentTasksから削除するタイミング

削除するのは以下のタイミング
addRecentTaskLocked()のadd処理でMAX(=20)を超えた場合、古いものを削除 RecentsPanelView(=履歴表示)からSwipで削除した場合、ActivityManagerService#removeTask()をコール


こちらはRunningTasksとくらべ、ユーザー操作に近いトリガーとなります。


getRecentTasks()は『起動アプリ履歴に残っているTask一覧』であって、
現在、起動中と意味でTask情報を取得したい場合はgetRunningTasks()を使用する方が好ましいと思います。

自アプリケーションがTOPかどうか確認する方法(2)

ActivityManager#getRunningTasks()の情報って?

ActivityManager#getRunningTasks()の情報が何かソースを見てみます。

モジュールを辿っていくと、ActivityManagerService#getTasks()をコールします。

ActivityManager#getRunningTasks()
- ActivityManagerService#getTasks()


ActivityManagerService#getTasks()は以下のような処理。


    public List getTasks(int maxNum, int flags,
                         IThumbnailReceiver receiver) {
        ArrayList list = new ArrayList();
                     :
                     :
            int pos = mMainStack.mHistory.size()-1;
            ActivityRecord next =
                pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null;
            ActivityRecord top = null;
            TaskRecord curTask = null;
            int numActivities = 0;
            int numRunning = 0;
            while (pos >= 0 && maxNum > 0) {
                     :
                     :
            }
    }
mMainStack.mHistoryのActivityRecord情報からListを生成します。



ActivityRecordをmMainStack.mHistoryに追加するタイミング

mMainStack.mHistoryのadd()をコールするソースはActivityStack.java。
もともとActivityManagerServiceに記述されていたソースのうち、Stack情報に関わる操作などがActivityStack.javaに分離されました。

で、mMainStack.mHistoryのadd()をコールするのは以下のモジュール。


final boolean switchUser()
private final void startActivityLocked()
private final ActivityRecord resetTaskIfNeededLocked()
private final ActivityRecord moveActivityToFrontLocked()
final void moveTaskToFrontLocked()
final boolean moveTaskToBackLocked()
Activityをstartしたり、起動済みのActivityをFrontに移行する際に呼ばれていました。



ActivityRecordをmMainStack.mHistoryから削除するタイミング

リストから明示的に削除を行っているのは以下のモジュールです。

removeActivityFromHistoryLocked
このモジュールはActivityがDestroyされるときにコールされます。

final boolean destroyActivityLocked
つまり、アプリ内のすべてのActivityがDestroyされるとmMainStack.mHistory内から抹殺され、 (onStop状態でも)Activityが一つでも残っていればmMainStack.mHistoryに存在することとなります。

自アプリケーションがTOPかどうか確認する方法

以前の記事でApplication単位でresume/pauseのハンドリングが不可能とわかりました。
 http://baroqueworksdevjp.blogspot.jp/2012/08/applicationonterminate.html


では、他に何かいい方法がないか調べてみました。


ActivityManagerの公開APIを使う

以下のような、Task情報を取得できるAPIをActivityのonStop()でコールしてみました。
    private void checkRecentTasks(){
        Log.e("","checkRecentTasks");

        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        List<RecentTaskInfo> taskInfo = am.getRecentTasks(10, ActivityManager.RECENT_WITH_EXCLUDED);
        for( RecentTaskInfo info : taskInfo){
            Log.e("", "" + info.baseIntent.getComponent().getPackageName());
        }
    }
    
    private void checkRunningTasks(){
        Log.e("","checkRunningTasks");
  
        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningTaskInfo> taskInfo = am.getRunningTasks(10);
        for( RunningTaskInfo info : taskInfo){
            Log.e("","" + info.topActivity.getPackageName());
        }
    }


Homeキー押下によりSampleアプリをBackGroudにさせたlogです。

checkRecentTasks
08-27 21:12:13.314: E/(7511): com.android.launcher
08-27 21:12:13.314: E/(7511): jp.baroqueworksdev.sampleapp
08-27 21:12:13.314: E/(7511): com.android.packageinstaller
08-27 21:12:13.314: E/(7511): jp.r246.twicca
08-27 21:12:13.314: E/(7511): com.alphonso.pulse
08-27 21:12:13.314: E/(7511): com.chnavi.android2ch
08-27 21:12:13.314: E/(7511): jp.mixi
08-27 21:12:13.314: E/(7511): com.facebook.katana
08-27 21:12:13.314: E/(7511): com.android.chrome

checkRunningTasks
08-27 21:12:13.321: E/(7511): com.android.launcher
08-27 21:12:13.321: E/(7511): jp.baroqueworksdev.sampleapp
08-27 21:12:13.321: E/(7511): jp.r246.twicca
08-27 21:12:13.321: E/(7511): com.alphonso.pulse
08-27 21:12:13.321: E/(7511): com.chnavi.android2ch
08-27 21:12:13.321: E/(7511): jp.mixi
08-27 21:12:13.321: E/(7511): com.facebook.katana
08-27 21:12:13.321: E/(7511): com.android.chrome


直近に起動したアプリ順に並んでいました。
Android Developer Siteにも以下のように書いています。
 with the most recent being first and older ones after in order.


上記のようなTaskリストを取得して、自アプリがTOPかどうかチェックするのも一つの手段かと思います。.
ただし、ロック画面が表示されている場合、TOPにいても「画面上表示されておらずユーザーには見えない」ことを忘れずに。

2012年8月25日土曜日

DTI ServersMan SIM 3G 100 が届きました

500円SIMのニュースで話題になった、DTIの「ServersMan SIM 3G 100」を購入しました。




申し込みから到着まで


到着までの時系列
 8/20(月) DTI公式ページから申し込み http://dream.jp/
 8/22) 発送 (※ヤマト運輸 東京都台東区からでした)
 8/25(土) 到着


Galaxy Nexus(※海外版)に挿してみた

Galaxy NexusにSIMを挿入してみました。 DTIはdocomoさんのMVNOなので、APN設定にmopera UI / SPモードが標準で表示されていました。


DTIのAPN設定を完了し通信状態となっても、アンテナピクトが更新されませんでした。
これは端末側のソフトウェアの問題なので気にしない、気にしない。



テザリング

Galaxy Nexus(※海外版)でテザリングができるかどうか試してみました。




問題なくテザリング動作可能でした。
Docomo版端末は制限事項があるみたいなのでご自身で調べてみてください。


使ってみた感想

Twitterやメールなどテキストベースなら問題ない通信速度です。
ただ、Facebookやmixiなど画像を多く含むアプリケーションでは、ストレスを感じてしまいます。
100kのベストエフォートなんで、こんな感じです。

2012年8月24日金曜日

Application#onTerminate()がコールされない


(Activityではなく)Application単位でresume/pauseのハンドリングが可能か調査を行いました。
結論からいうと、frameworksからコールされる明示的なモジュールはありませんでした。


で、気になったのがApplication#onTerminate()というモジュール。
Android Developerには以下のように記載されています。

http://developer.android.com/reference/android/app/Application.html#onTerminate()

This method is for use in emulated process environments. It will never be called on a production Android device, where processes are removed by simply killing them; no user code (including this callback) is executed when doing so.

そう、Androidデバイスでは呼ばれることがないと書かれています。
じゃあ、何のために存在するのでしょう?

Application#onTerminate()をコールする箇所をgrepすると、ActivityThread.javaにありました。

                case EXIT_APPLICATION:
                    if (mInitialApplication != null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;


この処理が行われるのは、ActivityThread#scheduleExit()の内部からhandleメッセージが発行されたとき。

ActivityThread#scheduleExit()をコールするのは、以下のActivityManagerService.javaの2モジュール。


private final boolean attachApplicationLocked(IApplicationThread thread, int pid)
final void trimApplications()

モジュール内の処理を見てみると、以下のような処理でした。
                    if (app.pid > 0 && app.pid != MY_PID) {
                        EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
                                app.processName, app.setAdj, "empty");
                        Process.killProcessQuiet(app.pid);
                    } else {
                        try {
                            app.thread.scheduleExit();
                        } catch (Exception e) {
                            // Ignore exceptions.
                        }
                    }
ActivityThread#scheduleExit()をコールする条件は次の場合。
  • PIDが0未満
  • PIDがMY_PID(= System Process)


System Processと同じApplicationの場合、コールするみたいなんです。
そんなApplicationってありえるのでしょうか。。。


このソースを見る限り、商品として発売されているAndroidデバイスでは呼ばれないことは確実ですね。

2012年8月17日金曜日

Page Actionを作成する


Browser Actionは常にアイコンが表示されるのに対し、Page Actionは特定のWebサイトのみアイコンを表示させます。

必要なことは以下の2つ。

  • tabが切り替わった通知を受けるためにListenerを登録
  • manifest.jsonにpage_actionを記述


tabが切り替わった通知を受けるためにListenerを登録


Listenerに登録するモジュールをJavaScriptファイルに記述します。

background.js
// Called when the url of a tab changes.
function checkForValidUrl(tabId, changeInfo, tab) {
console.log("checkForValidUrl");
  if (tab.url.indexOf("http://www.blogger.com/blogger") > -1) {
    // ... show the page action.
    chrome.pageAction.show(tabId);
  }
};

// Listen for any changes to the URL of any tab.
chrome.tabs.onUpdated.addListener(checkForValidUrl);


上記のようなJavaScriptを作成し、manifest.jsonの"background"に記述します。
Chromeのtab変更通知を受けるためにchrome.tabsをコールするので"permissions"も追加

  "background": { "scripts": ["background.js"] },
  "permissions": [
    "tabs","http://*/*"
  ],


manifest.jsonにpage_actionを記述


Page Actionを指定するために、browser_actionからpage_actionに変更します。
tabのListener登録を含めると、以下のようなmanifest.jsonとなります。


{
  "name": "Page Action Extension",
  "version": "1.0",
  "manifest_version": 2,
  "description": "Page Action Extension Test.",
  "background": { "scripts": ["background.js"] },
  "permissions": [
    "tabs","http://*/*"
  ],
  "page_action":
  {
    "default_icon": "icon.png",
    "default_title": "this URL!",
    "default_popup": "popup.html"
 }
}


正常に動作すると、以下のようにアドレスバーの右端にアイコンが表示されます。


Pop Upを表示する

Browser Actionのアイコンをクリックした際、Pop Upを表示してみます。

以下のように、manifest.jsonに「"default_popup": "popup.html"」を追記します。


{
  "name": "Browser Action Extension",
  "version": "1.0",
  "manifest_version": 2,
  "description": "Browser Action Extension Test.",
  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  }
}



以下のように、PopUpが表示されます。





Pop Upの中身はmanifest.jsonで指定したhtml内に記述します。

Google Chrome Extensionsを作成してみる


Google Chrome Extensionsを作成してみようと思い、以下の公式ページを参照しながら勉強中。
http://developer.chrome.com/extensions/getstarted.html

Chrome Extensionsに最低限必要なファイルは以下の2つ。
  • manifest.json
  • icon.png


manifest.jsonにはExtensionの名前やVersionなどを記載する。
以下のようなmanifest.jsonを作成してみます。

{
  "name": "Browser Action Extension",
  "version": "1.0",
  "manifest_version": 2,
  "description": "Browser Action Extension Test.",
  "browser_action": {
    "default_icon": "icon.png"
  }
}


chromeで読み込ませたところ、以下のようなアイコンが表示されました。


2012年8月15日水曜日

何気なく使っているFramework 第5回 NFCManager


確認OSバージョン

Android OS 2.3 GingerBread

NFCManagerの役割り

NfcAdapterのHelperを提供
NfcAdapter
  • Tagディスパッチを要求に使用するIntentの定義
  • フォアグラウンドでのTagディスパッチとフォアグランドでのNDEFプッシュ用の登録メソッドを提供
  • 現在、フォアグラウンドNDEFプッシュはピアツーピアのみのサポート
NfcService
  • intent-filterのActionが"android.nfc.action.TECH_DISCOVERED"のActivityへTagを配信

アプリケーションPackage『com.android.nfc』を確認中。
(作成中)

NFC規格一覧

android.nfc.techパッケージにNFCの規格に順じたクラスが存在します。

クラス名規格名備考
NfcANFC-A (ISO 14443-3A)
NfcBNFC-B (ISO 14443-3B)
NfcFNFC-F (JIS 6319-4) Felica
NfcVNFC-V (ISO 15693)
IsoDepISO-DEP (ISO 14443-4)
NdefNFC Forum data format
NdefFormatable
MifareClassicNFC-Aベースのデータ。MIFARE Pro,MIFARE PLUSなどがある
MifareUltralightMifareClassicからセキュリティといくつかのコマンドを除いたもの

f:id:baroqueworksdev:20110730025735p:image:w640

何気なく使っているFramework 第6回 画面ロック設定



確認OSバージョン

Android 2.3.5_r1

キーガード設定

Settings.apkの「Screen unlock security」から画面ロック設定ができる。
ロック解除を行うパターンやパスワードの情報は、frameworks層の"LockPatternUtils"にて管理する。

種別設定クラス備考
NoneChooseLockGeneric.java「Screen unlock security」のメニュー画面(PreferenceActivity)
パターンChooseLockPattern.java
PINChooseLockPassword.java数字のみのパスワード
PasswordChooseLockPassword.java英字/数字のパスワード


LockPatternUtilsの役割

  • パスワード情報をSHA-1とMD5を用いてハッシュコード化する
  • ハッシュコードをFileに保存、保存先は/data/system/XXX.key
  • ファイルからパスワード情報をreadし、入力したデータとチェックを行う。
  • DevicePolicyManagerにパスワードクオリティを保存する



DevicePolicyManagerに宣言されているパスワードクオリティ

種別クオリティ備考
NonePASSWORD_QUALITY_UNSPECIFIED 
パターンPASSWORD_QUALITY_SOMETHING
PINPASSWORD_QUALITY_NUMERIC数字のみのパスワード
PasswordPASSWORD_QUALITY_ALPHABETIC英字のみパスワード
PasswordPASSWORD_QUALITY_ALPHANUMERIC英字/数字のパスワード


f:id:baroqueworksdev:20110817001517p:image:w640

HoneyCombでのDisplayについて


stackoverflowにて、以下のような質問がありました。
How can I get android Honeycomb system’s screen width and height?


DisplayってStatusBar込みの画面サイズだったような。。。っと思い、
手元にあるNexus S と Xoomで確認をしました。

ログの出力方法

Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);

Log.e("","------------------ ");
Log.e("","display.getHeight() = " + display.getHeight());
Log.e("","display.getWidth() = " + display.getWidth());
Log.e("","------------------ ");
Log.e("","displayMetrics.heightPixels = " + displayMetrics.heightPixels);
Log.e("","displayMetrics.widthPixels = " + displayMetrics.widthPixels);
Log.e("","------------------ ");



以下のような結果となりました。
HoneyCombではSystemBarの高さ分だけ、Heightから引かれているようです。


端末vergetHeight()getWidth()
Nexus S2.3.5800480
Xoom3.27521280


GingerBreadまではStatusBarの領域まで描画できたのに対し、
HoneyCombはSystemBarが隠せない?のが影響しているのでしょうか。

何気なく使っているFramework 第7回 スクリーンショット


今回から確認OSバージョンをAndroid OS 4.0.1_r1に変更しました。


確認OSバージョン

Android OS 4.0.1_r1 ICS

スクリーンショット


ICSから端末単体でスクリーンショットが撮れるようになりました。
とりあえず、frameworks内の確認!!


スクリーンショットが実行されるまでの流れ


  1. Native層のInputDispatcherからinterceptKeyBeforeQueueingをコール
  2. 一旦、Java層にコールバック
  3. InputManagerを経てPhoneWindowManager#interceptKeyBeforeQueueing

    1. interceptKeyBeforeQueueing
    2. interceptScreenshotChord()
    3. mScreenshotChordLongPress#run
    4. takeScreenshot()
  4. SystemUIに新たに追加されたService、TakeScreenshotService

    1. run()
  5. GlobalScreenshot#takeScreenshot
  6. Surface.screenshotをコールし画面イメージのBitmap取得
  7. アニメーションして保存


確認しておいた方がいいソース

\frameworks\base\services\input
- InputDispatcher.cpp
- InputManager.cpp
\frameworks\base\policy\src\com\android\internal\policy\impl
- PhoneWindowManager.java
\frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot
- GlobalScreenshot.java
- TakeScreenshotService.java


簡易クラス図


f:id:baroqueworksdev:20111204004218p:image:w640

何気なく使っているFrameworks 第9回 CPU使用状況を表示/ addView


CPU使用状況を表示

ICSから開発者向けオプションに「CPU使用状況を表示」が追加されました。

f:id:baroqueworksdev:20120124001631p:image:w360


Settingsで設定すると

以下の処理順でサービス:LoadAverageServiceが起動します。
このサービス内でProcessから取得した値をオーバーレイ表示を行っています。

値が何を意味するのかはProcessStats.javaをみればわかります。
ついでに、Viewの追加→WindowManagerServiceまでの流れも要チェックです。

特にViewRoot.javaがViewRootImpl.javaへ変わり、中身もそれなりに修正されています。

DevelopmentSettings#onPreferenceTreeClick
- writeCpuUsageOptions()
- startService "com.android.systemui.LoadAverageService" of SysttemUI.
- LoadAverageService#onCreate()
- new LoadView extends ProcessStats
- new Stats
- WindowManager#addView() with Overlay layer of SECURE_SYSTEM_OVERLAY_LAYER
- WindowManagerImpl#addView
- ViewRootImpl#setView
- IWindowSession#add
- Session#add
- WindowManagerService#addWindow


確認しておいた方がいいソース

\packages\apps\Settings\src\com\android\settings
  • DevelopmentSettings.java

\frameworks\base\packages\SystemUI\src\com\android\systemui
  • LoadAverageService.java

\frameworks\base\core\java\com\android\internal\os
  • ProcessStats.java

\frameworks\base\core\java\android\view
  • ViewRootImpl.java

\frameworks\base\services\java\com\android\server\wm
  • WindowManagerService.java

\frameworks\base\policy\src\com\android\internal\policy\impl
  • PhoneWindowManager.java


簡易クラス図

f:id:baroqueworksdev:20120124002429p:image:w640

PhoneモードとTabletモードの切り分け


Android 4.x(Ice Cream Sandwich)はPhoneとTablet、両方のOSとして使用されます。
では、どこでPhone/Tabletの切り分けはどこで行っているか確認します。

Phone/Tabletの切り分け

起動処理中、以下の処理でFrameworks内のDisplaySizeを初期化します。
ServerThread#run()
- WindowManagerService#displayReady
- PhoneWindowManagerService#setInitialDisplaySize


PhoneWindowManagerService#setInitialDisplaySizeのそれらしき処理があります。

// Determine whether the status bar can hide based on the size
// of the screen.  We assume sizes > 600dp are tablets where we
// will use the system bar.
int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / DisplayMetrics.DENSITY_DEVICE;
mStatusBarCanHide = shortSizeDp < 600;


ソースは
 スクリーンのswWidth * DisplayMetrics.DENSITY_DEFAULT
 DisplayMetrics.DENSITY_DEVICE が600未満かどうか
と記述されています。


DisplayMetrics.javaに記述されている値はこんな感じ。
    /**
     * Standard quantized DPI for medium-density screens.
     */
    public static final int DENSITY_MEDIUM = 160;

    /**
     * The reference density used throughout the system.
     */
    public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;

    /**
     * The device's density.
     * @hide becase eventually this should be able to change while
     * running, so shouldn't be a constant.
     */
     public static final int DENSITY_DEVICE = getDeviceDensity();

     private static int getDeviceDensity() {
         // qemu.sf.lcd_density can be used to override ro.sf.lcd_density
         // when running in the emulator, allowing for dynamic configurations.
         // The reason for this is that ro.sf.lcd_density is write-once and is
         // set by the init process when it parses build.prop before anything else.
         return SystemProperties.getInt("qemu.sf.lcd_density",
         SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT));
     }


crespo(Nexus s)の場合、
 swWidth = 480
 ro.sf.lcd_density=240
 shortSizeDp = 480 * 160 / 240 = 320

よって、Phoneモードとなる。


maguro(Galaxy Nexus)の場合、
 swWidth = 720
 ro.sf.lcd_density=320
 shortSizeDp = 720 * 160 / 320 = 360

よって、Phoneモードとなる。

maguro(Galaxy Nexus)でTabletモードで起動したい場合、
lcd_densityを320から160に変更すれば可能なはず。

 swWidth = 720
 ro.sf.lcd_density=160
 shortSizeDp = 720 * 160 / 160 = 720



確認しておきたいソース


\frameworks\base\policy\src\com\android\internal\policy\impl
- PhoneWindowManagerService.java

\frameworks\base\core\java\android\util
- DisplayMetrics.java

\frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar
- この配下全部 StatusBar / SystemBarの切り分けなど。

NavigationBarの有無判定


アプリケーション作成時に「NavigationBarが表示されている端末かどうか?」の判定が必要となった場合、どうすればいいか確認します。

以下のモジュールがFrameworks内にあります
 PhoneWindowManager#hasNavigationBar()
 WindoManagerService#hasNavigationBar()

このモジュールを使用しているソースがあるかどうかチェック。
アプリケーションで使えそうなモジュールはViewConfigurationというクラス。

public class ViewConfiguration {
    private ViewConfiguration(Context context) {
             :
        if (!sHasPermanentMenuKeySet) {
            IWindowManager wm = Display.getWindowManager();
            try {
                sHasPermanentMenuKey = wm.canStatusBarHide() && !wm.hasNavigationBar();
                sHasPermanentMenuKeySet = true;
            } catch (RemoteException ex) {
                sHasPermanentMenuKey = false;
            }
        }
             :
    }

    public boolean hasPermanentMenuKey() {
        return sHasPermanentMenuKey;
    }
}


hasPermanentMenuKey()というmethodは
Permanentな(常に押下できる)MenuKeyは存在する/しないか判定するmethod。

sHasPermanentMenuKeyは以下の条件で値が決定する。
wm.canStatusBarHide()は「Phone/Tabletの判定」、wm.hasNavigationBar()は「NavigationBarの有無判定」なので、
Tabletも場合、そのそもNavigationBarは非対応なので、falseとなる。
Phone ModeでかつNavigationBarがない場合、trueとなる。
Phone ModeでかつNavigationBarがある場合、falseとなる。

となる。

と、いうことでアプリ側で以下のように値を取得すればOK。

//hasPermanentMenuKey == true はNavigationBarが非表示
//hasPermanentMenuKey == false はNavigationBarが表示
boolean isNavigationBar = ! ViewConfiguration.get(this).hasPermanentMenuKey();


関連リンク:

 何気なく使っているFrameworks 第10回 NavigationBar / ナビゲーションバー

 EmulatorでNavigationBarを表示する

プリインストールアプリの無効化



f:id:baroqueworksdev:20120215235245p:image:w360


設定のアプリ一覧からプリインストールの無効化ができます。
ただし、すべてのプリインストールが無効にできるわけではありません。


以下のソースを確認。

packages\apps\Settings\src\com\android\settings\applications
  • InstalledAppDetails.java

    private void initUninstallButtons() {
        mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
        boolean enabled = true;
        if (mUpdatedSysApp) {
            mUninstallButton.setText(R.string.app_factory_reset);
        } else {
            if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                enabled = false;
                if (SUPPORT_DISABLE_APPS) {
                    try {
                        // Try to prevent the user from bricking their phone
                        // by not allowing disabling of apps signed with the
                        // system cert and any launcher app in the system.
                        PackageInfo sys = mPm.getPackageInfo("android",
                                PackageManager.GET_SIGNATURES);
                        Intent intent = new Intent(Intent.ACTION_MAIN);
                        intent.addCategory(Intent.CATEGORY_HOME);
                        intent.setPackage(mAppEntry.info.packageName);
                        List homes = mPm.queryIntentActivities(intent, 0);
                        if ((homes != null && homes.size() > 0) ||
                                (mPackageInfo != null && mPackageInfo.signatures != null &&
                                        sys.signatures[0].equals(mPackageInfo.signatures[0]))) {
                            // Disable button for core system applications.
                            mUninstallButton.setText(R.string.disable_text);
                        } else if (mAppEntry.info.enabled) {
                            mUninstallButton.setText(R.string.disable_text);
                            enabled = true;
                        } else {
                            mUninstallButton.setText(R.string.enable_text);
                            enabled = true;
                        }
                    } catch (PackageManager.NameNotFoundException e) {
                        Log.w(TAG, "Unable to get package info", e);
                    }
                }
            } else {
                mUninstallButton.setText(R.string.uninstall_text);
            }
        }
        // If this is a device admin, it can't be uninstall or disabled.
        // We do this here so the text of the button is still set correctly.
        if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
            enabled = false;
        }
        mUninstallButton.setEnabled(enabled);
        if (enabled) {
            // Register listener
            mUninstallButton.setOnClickListener(this);
        }
    }


以下のプリインストールアプリは無効化できません。
  • マーケットからUpdateを行った
  • Intent.CATEGORY_HOMEのActivityを持っている (= Launcher Application)
  • System と同じSignature を持っている ( = System Application)


Disableを選択すると、PackegeManager#setApplicationEnabledSetting()がよばれ無効化されます。
無効化されたアプリは、アプリ一覧に表示されなくなります。

2012年7月21日土曜日

各TAGとBUILD_ID

AOSPに新しいTAGが出現しました。
ざっと確認したところ、以下のような感じです。

tagBUILD_ID確認機種
android-4.1.1_r1JRO03CGalaxy Nexus
android-4.1.1_r1.1JRO03DNexus 7
android-4.1.1_r2JRO03ENexus S
android-4.1.1_r3JRO03HXoom Wifi model(wingray)*1
*1:7/29 追記

2012年7月5日木曜日

ASSIT機能アプリケーションを作成する

Goolge Nowのように、端末のASSIT機能として動作するアプリケーションを作成する方法です。
Google Now(= Intent.ACTION_ASSIST)の起動トリガーでも登場した、
Intent.ACTION_ASSISTを使用します。


http://developer.android.com/reference/android/content/Intent.html#ACTION_ASSIST


AndroidManifest.xmlのactivityタグに記載しておけば起動可能です。

        <activity
            android:name=".TestApp"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.ASSIST" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>


こんな感じでResolverに選択候補として表示されました。


f:id:baroqueworksdev:20120706015251p:image:w240


と、いうことでGoogle Nowの代わりに、
iPhoneの『Siri』やDocomoさんの『しゃべってコンシェル』のような、
3rdベンダーアプリもASSIT機能として搭載可能ですね!!

Google Now(= Intent.ACTION_ASSIST)の起動トリガー

以下の記事でNavigationBarからGoogle Nowを起動するトリガーを発見しました。

Android 4.1で追加されたNAVIGATION_BAR_PANEL_LAYERについて
http://d.hatena.ne.jp/baroqueworksdev/20120630/1341088267


今回はもう少し、調査してみます。
SearchPanelViewからGoogle Nowを起動する処理はこんな感じ。
<SDK>\sources\android-16\com\android\systemui\SearchPanelView.java

    private void startAssistActivity() {
        // Close Recent Apps if needed
        mBar.animateCollapse(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL);
        // Launch Assist
        Intent intent = SearchManager.getAssistIntent(mContext);
        if (intent == null) return;
        try {
            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
                    R.anim.search_launch_enter, R.anim.search_launch_exit,
                    getHandler(), this);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(intent, opts.toBundle());
        } catch (ActivityNotFoundException e) {
            Slog.w(TAG, "Activity not found for " + intent.getAction());
            onAnimationStarted();
        }
    }


どうやら、Google Nowは「Assist」というカテゴリーに属しているようです。
SearchManager#getAssistIntent()というモジュールから、
対象アプリを取得してアプリ起動を行っていました。
SearchManagerのソースを確認。

<SDK>\sources\android-16\android\app\SearchManager.java

    /**
     * Gets an intent for launching installed assistant activity, or null if not available.
     * @return The assist intent.
     *
     * @hide
     */
    public static final Intent getAssistIntent(Context context) {
        PackageManager pm = context.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_ASSIST);
        ComponentName component = intent.resolveActivity(pm);
        if (component != null) {
            intent.setComponent(component);
            return intent;
        }
        return null;
    }

Intent.ACTION_ASSISTというアクションIntentを持つアプリを取得しています。
これはAPI 16で新たに追加されたアクションです。


SearchManager#getAssistIntent()のコール個所

ASSITアプリを起動するソースは以下の3か所でした。

com\android\internal\policy\impl\LockScreen.java
  → ロック画面から起動
com\android\systemui\SearchPanelView.java
  → NavigationBarのタップ処理から起動
com\android\internal\policy\impl\PhoneWindowManager.java
  → KeyEvent.KEYCODE_ASSISTが押下されたときに起動

KeyEventにKEYCODE_ASSISTが追加されています!!
KeyEvent.KEYCODE_ASSISTが押下されたときに処理はこちら。

com\android\internal\policy\impl\PhoneWindowManager.java

    /** {@inheritDoc} */
    @Override
    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
           :
           :
        } else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
            if (down) {
                if (repeatCount == 0) {
                    mAssistKeyLongPressed = false;
                } else if (repeatCount == 1) {
                    mAssistKeyLongPressed = true;
                    if (!keyguardOn) {
                         launchAssistLongPressAction();
                    }
                }
            } else {
                if (mAssistKeyLongPressed) {
                    mAssistKeyLongPressed = false;
                } else {
                    if (!keyguardOn) {
                        launchAssistAction();
                    }
                }
            }
            return -1;
        }
           :
           :
    }

    private void launchAssistLongPressAction() {
        performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);

        // launch the search activity
        Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            // TODO: This only stops the factory-installed search manager.  
            // Need to formalize an API to handle others
            SearchManager searchManager = getSearchManager();
            if (searchManager != null) {
                searchManager.stopSearch();
            }
            mContext.startActivity(intent);
        } catch (ActivityNotFoundException e) {
            Slog.w(TAG, "No activity to handle assist long press action.", e);
        }
    }

    private void launchAssistAction() {
        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);
        Intent intent = SearchManager.getAssistIntent(mContext);
        if (intent != null) {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                    | Intent.FLAG_ACTIVITY_SINGLE_TOP
                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            try {
                mContext.startActivity(intent);
            } catch (ActivityNotFoundException e) {
                Slog.w(TAG, "No activity to handle assist action.", e);
            }
        }
    }



KeyEvent.KEYCODE_ASSISの定義はこちら。
<SDK>\sources\android-16\android\view\KeyEvent.java

    /** Key code constant: Assist key.
     * Launches the global assist activity.  Not delivered to applications. */
    public static final int KEYCODE_ASSIST          = 219;

PhoneWindowManager#interceptKeyBeforeDispatching()で return -1を行っていますし、
コメントにあるように、アプリケーションには配信されないキーコードです。

2012年6月30日土曜日

PhoneモードとTabletモードの切り分け

ICSから変更されています。
これは注目すべき変更点です。

Phone/Tabletの切り分け

PhoneWindowManagerService#setInitialDisplaySizeの処理を確認

        // SystemUI (status bar) layout policy
        int shortSizeDp = shortSize
                * DisplayMetrics.DENSITY_DEFAULT
                / DisplayMetrics.DENSITY_DEVICE;

        if (shortSizeDp < 600) {
            // 0-599dp: "phone" UI with a separate status & navigation bar
            mHasSystemNavBar = false;
            mNavigationBarCanMove = true;
        } else if (shortSizeDp < 720) {
            // 600-719dp: "phone" UI with modifications for larger screens
            mHasSystemNavBar = false;
            mNavigationBarCanMove = false;
        } else {
            // 720dp: "tablet" UI with a single combined status & navigation bar
            mHasSystemNavBar = true;
            mNavigationBarCanMove = false;
        }


ICSでは600dp以上をTabletとして扱っていました。
JBでTabletとして認識するには720dp以上が必要です。

Android 4.1で追加されたNAVIGATION_BAR_PANEL_LAYERについて

f:id:baroqueworksdev:20120701053040p:image:w240

以下の記事にて、表示レイヤーの確認を行いました。

JBの表示レイヤーについて
http://d.hatena.ne.jp/baroqueworksdev/20120630/1341084903

今回は新規追加レイヤー、NAVIGATION_BAR_PANEL_LAYERの確認です。


NAVIGATION_BAR_PANEL_LAYERの使用箇所

PhoneWindowManager#windowTypeToLayerLw()にてwindowTypeからLayerの値に変換するので、grepはTYPE_NAVIGATION_BAR_PANELで。
引っかかるのは以下の3ファイルでした。

SystemUIが使用していますね。

  • com\android\systemui\statusbar\phone\PhoneStatusBar.java
  • com\android\systemui\statusbar\tablet\TabletStatusBar.java
  • com\android\systemui\statusbar\tablet\TabletTicker.java

PhoneStatusBar.javaに焦点をあてて調査します。
TYPE_NAVIGATION_BAR_PANELを使用しているのは以下のモジュール。
BaseStatusBar#updateSearchPanel
↓
PhoneStatusBar#getSearchLayoutParams() ←★ここでWindowLayoutのパラメータとして使用

BaseStatusBar#updateSearchPanelのモジュール内で、
mSearchPanelViewというViewをWindowにaddViewしていました。

    protected void updateSearchPanel() {
        // Search Panel
        boolean visible = false;
        if (mSearchPanelView != null) {
            visible = mSearchPanelView.isShowing();
            WindowManagerImpl.getDefault().removeView(mSearchPanelView);
        }

        // Provide SearchPanel with a temporary parent to allow layout params to work.
        LinearLayout tmpRoot = new LinearLayout(mContext);
        mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_search_panel, tmpRoot, false);
        mSearchPanelView.setOnTouchListener(
                 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
        mSearchPanelView.setVisibility(View.GONE);

        WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());

        WindowManagerImpl.getDefault().addView(mSearchPanelView, lp);
        mSearchPanelView.setBar(this);
        if (visible) {
            mSearchPanelView.show(true, false);
        }
    }



SearchPanelViewってなんだ?


mSearchPanelViewの表示タイミングは以下のソースを参照。
com\android\systemui\statusbar\phone\PhoneStatusBar.java


    @Override
    public void showSearchPanel() {
        super.showSearchPanel();
        WindowManager.LayoutParams lp =
            (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams();
        lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        WindowManagerImpl.getDefault().updateViewLayout(mNavigationBarView, lp);
    }


    private Runnable mShowSearchPanel = new Runnable() {
        public void run() {
            showSearchPanel();
        }
    };
    View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            switch(event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!shouldDisableNavbarGestures()) {
                    mHandler.removeCallbacks(mShowSearchPanel);
                    mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff);
                }
            break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mHandler.removeCallbacks(mShowSearchPanel);
            break;
        }
        return false;
        }
    };

    private void prepareNavigationBarView() {
        mNavigationBarView.reorient();

        mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
        mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPanel);
        mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener);
        updateSearchPanel();
    }



ようはHomeキーを一定時間押し続けると、SearchPanelViewが表示されます。
Viewの処理はこちらのソースを参照

\sources\android-16\com\android\systemui\SearchPanelView.java




JBの表示レイヤーについて


Android SDKのAPI 16が公開されました。
一緒にソースもDLできるので、毎度おなじみの表示レイヤーのチェックを行いました。

確認OSバージョン

Android OS 4.1 JB

表示レイヤー

OSのメジャーアップデートなので、表示レイヤーの変更点があるかどうか確認しました。
ソース
SDK\sources\android-16\com\android\internal\policy\impl
- PhoneWindowManager.java
(★ = JBにて新規追加されたレイヤー)
レイヤー名用途
2WALLPAPER_LAYER壁紙
2APPLICATION_LAYER一般アプリケーション
3PHONE_LAYER着信などの電話用
4SEARCH_BAR_LAYER検索バー
5SYSTEM_DIALOG_LAYER電源OFFダイアログなど
6TOAST_LAYERトースト表示
7PRIORITY_PHONE_LAYERSIMエラー表示など
8SYSTEM_ALERT_LAYERANRやLowバッテリー通知
9INPUT_METHOD_LAYER文字入力UI
10INPUT_METHOD_DIALOG_LAYER文字入力UIのダイアログ
11KEYGUARD_LAYERキーガード表示
12KEYGUARD_DIALOG_LAYERシャットダウン中やSIMロック表示、キーガード表示中の電源OFFダイアログなど
13SCREENSAVER_LAYERスクリーンセーバー★
14STATUS_BAR_SUB_PANEL_LAYERPhone用expandしたパネル
15STATUS_BAR_LAYERStatusBar
16STATUS_BAR_PANEL_LAYERStatusBarをexpandしたパネル
17VOLUME_OVERLAY_LAYERボリューム変更
18SYSTEM_OVERLAY_LAYERキーガードより上位に表示するシステムオーバーレイ
19NAVIGATION_BAR_LAYERナビゲーションBar
20NAVIGATION_BAR_PANEL_LAYERナビゲーションBarの上に表示するために必要なパネル(searchなど)★
21SYSTEM_ERROR_LAYERシステムエラー通知
22DRAG_LAYERドラッグ&ドロップ操作用
23SECURE_SYSTEM_OVERLAY_LAYER
24BOOT_PROGRESS_LAYERBoot中のDialog表示
25POINTER_LAYERマウスポインター
26HIDDEN_NAV_CONSUMER_LAYERFakeWindow用


大きな変更点は
  1. SCREENSAVER_LAYERの追加
  2. NAVIGATION_BAR_PANEL_LAYERの追加

NAVIGATION_BAR_PANEL_LAYERのの使用箇所はべっと調査します。

2012年3月31日土曜日

AOSPのTag間のdiffとlogを取得する



AOSPにandroid-4.0.4_r1.1が現れました。

android-4.0.3_r1とのdiffとlogの取得を行いました。


diffの取得

ファイル差分すべてを取得
$ repo forall -p -c git diff android-4.0.3_r1..android-4.0.4_r1.1 > diff_android-4.0.3_r1_android-4.0.4_r1.1.txt


変更されたファイルの一覧と変更行数を取得
$ repo forall -p -c git diff --stat android-4.0.3_r1..android-4.0.4_r1.1 > diff_android-4.0.3_r1_android-4.0.4_r1.1_stat.txt


ディレクトリ毎の変更量(%)を取得
$ repo forall -p -c git diff --dirstat android-4.0.3_r1..android-4.0.4_r1.1 > diff_android-4.0.3_r1_android-4.0.4_r1.1_dirstat.txt


logの取得


TAG間のlog(コミットタイトル)を取得
$ repo forall -p -c git log --oneline --no-merges android-4.0.3_r1..android-4.0.4_r1.1 > log_android-4.0.3_r1_android-4.0.4_r1.1.txt


どうぞ


diffとlogの実行結果を以下にUploadしました。よろしければ、どうぞ。

https://docs.google.com/leaf?id=0BwdCdBWuE_7cSHE0Mkpodm5TN3VMZjFmOTJ2QUU0UQ