2012年1月31日火曜日

Nexus SをAndroid 4.0.3にUpdateしました。
波形をとりながら動作確認中を行ったところ、面白い発見をしたので記事にしました。


キーガードロック画面のNFC動作
まずはAndroid2.3.6 GingerBread版Nexus Sの波形です。


電源キーによりスクリーンOFF → ONを行いキーガードロック画面を表示しました。
スクリーンON中はNFC電源ONになり、その波形が現れています。
約5秒後、再びスクリーンOFFになりました。


次はAndroid4.0.3 Ice Cream Sandwich版Nexus Sの波形です。


同じ操作でキーガードロック画面を表示しました。
しかし、スクリーンONになってもNFC電源ONの波形が現れません!

どうやら、ICSの変更でキーガードロック中はNFC電源が入らないように修正されているようです。


プログラムを確認してみましょう。
まずは、Android2.3.6 GingerBreadのプログラムです。
スクリーンON/OFFの動作処理を行っているのは以下のファイルです。
\packages\apps\Nfc\src\com\android\nfc
- NfcService.java

private class EnableDisableDiscoveryTask extends AsyncTask {
@Override
protected Void doInBackground(Boolean... enable) {
if (enable != null && enable.length > 0 && enable[0]) {
synchronized (NfcService.this) {
mScreenOn = true;
applyRouting();
}
} else {
:
}
return null;
}
}

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
:
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
:
new EnableDisableDiscoveryTask().execute(new Boolean(true));
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
:
}
}


以下の処理により、電源ONになりNFCタグ受信待ち状態となります。
  1. スクリーンONのIntentであるIntent.ACTION_SCREEN_ONを受信する
  2. EnableDisableDiscoveryTask().execute()をコール
  3. applyRouting()をコール
  4. mManager.enableDiscovery(DISCOVERY_MODE_READER)をコール



つぎは、Android4.0.3 Ice Cream Sandwichのプログラム確認。
\packages\apps\Nfc\src\com\android\nfc
- NfcService.java

/**
* Read mScreenState and apply NFC-C polling and NFC-EE routing
*/
void applyRouting(boolean force) {
synchronized (this) {
:
// configure NFC-C polling
if (mScreenState >= POLLING_MODE) {
if (force || !mNfcPollingEnabled) {
Log.d(TAG, "NFC-C ON");
mNfcPollingEnabled = true;
mDeviceHost.enableDiscovery();
}
} else {
if (force || mNfcPollingEnabled) {
Log.d(TAG, "NFC-C OFF");
mNfcPollingEnabled = false;
mDeviceHost.disableDiscovery();
}
}
}
}



class ApplyRoutingTask extends AsyncTask {
@Override
protected Void doInBackground(Integer... params) {
synchronized (NfcService.this) {
if (params == null || params.length != 1) {
// force apply current routing
applyRouting(true);
return null;
}
mScreenState = params[0].intValue();

boolean needWakelock = mScreenState == SCREEN_STATE_OFF;
if (needWakelock) {
mWakeLock.acquire();
}
applyRouting(false);
if (needWakelock) {
mWakeLock.release();
}
return null;
}
}
}

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
:
} else if (action.equals(Intent.ACTION_SCREEN_ON)
|| action.equals(Intent.ACTION_SCREEN_OFF)
|| action.equals(Intent.ACTION_USER_PRESENT)) {
// Perform applyRouting() in AsyncTask to serialize blocking calls
int screenState = SCREEN_STATE_OFF;
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
screenState = SCREEN_STATE_OFF;
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
screenState = mKeyguard.isKeyguardLocked() ?
SCREEN_STATE_ON_LOCKED : SCREEN_STATE_ON_UNLOCKED;
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
screenState = SCREEN_STATE_ON_UNLOCKED;
}
new ApplyRoutingTask().execute(Integer.valueOf(screenState));
} else if (action.equals(ACTION_MASTER_CLEAR_NOTIFICATION)) {
:
}
}


以下のように処理が変更され、スクリーンONでは電源ONとなりません。
  1. スクリーンONのIntentであるIntent.ACTION_SCREEN_ONを受信する
  2. mKeyguard.isKeyguardLocked()でキーガード状態かどうか確認する
  3. キーガード状態の場合、SCREEN_STATE_ON_LOCKEDを引数としてApplyRoutingTask().execute()をコール
  4. applyRouting(false)をコール
  5. mScreenStateがSCREEN_STATE_ON_LOCKEDなので、mDeviceHost.enableDiscovery()はコールしない

電源ONとなるタイミングはIntent.ACTION_USER_PRESENTを受信した時、
つまり、キーガードが解除された時に電源ONとなります。

キーガード中のNFC動作 GingerBread と Ice Cream Sandwichの違い

Nexus SをAndroid 4.0.3にUpdateしました。
波形をとりながら動作確認中を行ったところ、面白い発見をしたので記事にしました。


キーガードロック画面のNFC動作
まずはAndroid2.3.6 GingerBread版Nexus Sの波形です。


電源キーによりスクリーンOFF → ONを行いキーガードロック画面を表示しました。
スクリーンON中はNFC電源ONになり、その波形が現れています。
約5秒後、再びスクリーンOFFになりました。


次はAndroid4.0.3 Ice Cream Sandwich版Nexus Sの波形です。


同じ操作でキーガードロック画面を表示しました。
しかし、スクリーンONになってもNFC電源ONの波形が現れません!

どうやら、ICSの変更でキーガードロック中はNFC電源が入らないように修正されているようです。


プログラムを確認してみましょう。
まずは、Android2.3.6 GingerBreadのプログラムです。
スクリーンON/OFFの動作処理を行っているのは以下のファイルです。
\packages\apps\Nfc\src\com\android\nfc
- NfcService.java

private class EnableDisableDiscoveryTask extends AsyncTask {
@Override
protected Void doInBackground(Boolean... enable) {
if (enable != null && enable.length > 0 && enable[0]) {
synchronized (NfcService.this) {
mScreenOn = true;
applyRouting();
}
} else {
:
}
return null;
}
}

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
:
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
:
new EnableDisableDiscoveryTask().execute(new Boolean(true));
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
:
}
}


以下の処理により、電源ONになりNFCタグ受信待ち状態となります。
  1. スクリーンONのIntentであるIntent.ACTION_SCREEN_ONを受信する
  2. EnableDisableDiscoveryTask().execute()をコール
  3. applyRouting()をコール
  4. mManager.enableDiscovery(DISCOVERY_MODE_READER)をコール



つぎは、Android4.0.3 Ice Cream Sandwichのプログラム確認。
\packages\apps\Nfc\src\com\android\nfc
- NfcService.java

/**
* Read mScreenState and apply NFC-C polling and NFC-EE routing
*/
void applyRouting(boolean force) {
synchronized (this) {
:
// configure NFC-C polling
if (mScreenState >= POLLING_MODE) {
if (force || !mNfcPollingEnabled) {
Log.d(TAG, "NFC-C ON");
mNfcPollingEnabled = true;
mDeviceHost.enableDiscovery();
}
} else {
if (force || mNfcPollingEnabled) {
Log.d(TAG, "NFC-C OFF");
mNfcPollingEnabled = false;
mDeviceHost.disableDiscovery();
}
}
}
}



class ApplyRoutingTask extends AsyncTask {
@Override
protected Void doInBackground(Integer... params) {
synchronized (NfcService.this) {
if (params == null || params.length != 1) {
// force apply current routing
applyRouting(true);
return null;
}
mScreenState = params[0].intValue();

boolean needWakelock = mScreenState == SCREEN_STATE_OFF;
if (needWakelock) {
mWakeLock.acquire();
}
applyRouting(false);
if (needWakelock) {
mWakeLock.release();
}
return null;
}
}
}

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
:
} else if (action.equals(Intent.ACTION_SCREEN_ON)
|| action.equals(Intent.ACTION_SCREEN_OFF)
|| action.equals(Intent.ACTION_USER_PRESENT)) {
// Perform applyRouting() in AsyncTask to serialize blocking calls
int screenState = SCREEN_STATE_OFF;
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
screenState = SCREEN_STATE_OFF;
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
screenState = mKeyguard.isKeyguardLocked() ?
SCREEN_STATE_ON_LOCKED : SCREEN_STATE_ON_UNLOCKED;
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
screenState = SCREEN_STATE_ON_UNLOCKED;
}
new ApplyRoutingTask().execute(Integer.valueOf(screenState));
} else if (action.equals(ACTION_MASTER_CLEAR_NOTIFICATION)) {
:
}
}


以下のように処理が変更され、スクリーンONでは電源ONとなりません。
  1. スクリーンONのIntentであるIntent.ACTION_SCREEN_ONを受信する
  2. mKeyguard.isKeyguardLocked()でキーガード状態かどうか確認する
  3. キーガード状態の場合、SCREEN_STATE_ON_LOCKEDを引数としてApplyRoutingTask().execute()をコール
  4. applyRouting(false)をコール
  5. mScreenStateがSCREEN_STATE_ON_LOCKEDなので、mDeviceHost.enableDiscovery()はコールしない

電源ONとなるタイミングはIntent.ACTION_USER_PRESENTを受信した時、
つまり、キーガードが解除された時に電源ONとなります。
Kaspersky Endpoint Security, which claims to be compatible with Lion, apparently is not.

Before upgrading to Lion I had no problems with Xcode or other application freezing and becoming "not responding". After upgrading, Xcode, which I use all day in my work, often freezes, sometimes other applications too. Usually it returns to normal state if I wait 1-5 minutes, but sometimes not and unsaved data is lost when I have to force quit Xcode (before I discovered how to quit Kaspersky's process). The really bad thing is that it's when saving that Kaspersky locks up the saving application so the chances of loosing valuable work is high.

If you are required to run Kaspersky on your Mac, I recommend that you do not upgrade to Lion until Kaspersky has fixed to support Lion's Autosave and Versions.

If Xcode or any other application locks up, open "Activity Monitor" and quit the process "kav". This will release the lock on the frozen application but (it looks like) Kaspersky will automatically restart so you have to repeat this every time an application freezes when saving data.

I'm not alone:
Cocoabuilder.com: Xcode4 and Lion causes freezes when saving
Stackoverflow.com: Xcode is very slow to save a file frequently beach balls

Waiting for a fix...

Lion + Kaspersky + Xcode = Trouble...

Kaspersky Endpoint Security, which claims to be compatible with Lion, apparently is not.

Before upgrading to Lion I had no problems with Xcode or other application freezing and becoming "not responding". After upgrading, Xcode, which I use all day in my work, often freezes, sometimes other applications too. Usually it returns to normal state if I wait 1-5 minutes, but sometimes not and unsaved data is lost when I have to force quit Xcode (before I discovered how to quit Kaspersky's process). The really bad thing is that it's when saving that Kaspersky locks up the saving application so the chances of loosing valuable work is high.

If you are required to run Kaspersky on your Mac, I recommend that you do not upgrade to Lion until Kaspersky has fixed to support Lion's Autosave and Versions.

If Xcode or any other application locks up, open "Activity Monitor" and quit the process "kav". This will release the lock on the frozen application but (it looks like) Kaspersky will automatically restart so you have to repeat this every time an application freezes when saving data.

I'm not alone:
Cocoabuilder.com: Xcode4 and Lion causes freezes when saving
Stackoverflow.com: Xcode is very slow to save a file frequently beach balls

Waiting for a fix...

2012年1月25日水曜日

In one of my apps I have data as csv files in the bundle and at when the app is run for the first time I parse these files and create a database using Core Data. During development I often added more data to the csv files, adjusted the Core Data classes and so on, so I needed a smooth way of telling whether the app was run for the first time or not since the last build so I can reconstruct the database.

I first tried another solution that didn't require the target name "MYTARGET" in the script which means you don't have to customize the script for every project but I couldn't get that to work in Xcode 4.2 but here is a script that works great for me. I will link to the source if I remember it later.

In the project navigator, select your project and then your target to the right.
In the "Info"-tab, create a new "Custom iOS Target Property" called "BuildNumber" and let it be a string.
Select the tab "Build Phases" and "Add Build Phase" down to the right.
Select "Add Run Script" and set the shell to "/bin/bash"
Paste in this script: (replace MYTARGET with the name of your target (usually same as name of project)

#!/bin/bash
buildNumber=$(/usr/libexec/PlistBuddy -c "Print BuildNumber" MYTARGET/MYTARGET-Info.plist)
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :BuildNumber $buildNumber" MYTARGET/MYTARGET-Info.plist

bundleShortVersion=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" MYTARGET/MYTARGET-Info.plist)
bundleVersion=$bundleShortVersion"."$buildNumber
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $bundleVersion" MYTARGET/MYTARGET-Info.plist

Now, each time you build your app the variable "BuildNumber" will be increased by 1 and your bundle version will be set to Version.BuildNumber. That is, if your app version is 1.0 and your on build number 8, the bundle version will be 1.0.8.

If you, in your app delegate or elsewhere, want to check if the "BuildNumber" has changed since the last run by comparing it with the last value, stored in NSUserDefaults, do something like:

int thisVersion = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"BuildNumber"] intValue];
int lastVersion = [[NSUserDefaults standardUserDefaults] integerForKey:@"lastVersion"];
[[NSUserDefaults standardUserDefaults] setInteger:thisVersion forKey:@"lastVersion"];
BOOL appIsNewVersion = lastVersion != thisVersion;

And there, you have a smooth, automated way of telling if you're on a new build and do the proper initializing, etc.

Auto incrementing build number in Xcode

In one of my apps I have data as csv files in the bundle and at when the app is run for the first time I parse these files and create a database using Core Data. During development I often added more data to the csv files, adjusted the Core Data classes and so on, so I needed a smooth way of telling whether the app was run for the first time or not since the last build so I can reconstruct the database.

I first tried another solution that didn't require the target name "MYTARGET" in the script which means you don't have to customize the script for every project but I couldn't get that to work in Xcode 4.2 but here is a script that works great for me. I will link to the source if I remember it later.

In the project navigator, select your project and then your target to the right.
In the "Info"-tab, create a new "Custom iOS Target Property" called "BuildNumber" and let it be a string.
Select the tab "Build Phases" and "Add Build Phase" down to the right.
Select "Add Run Script" and set the shell to "/bin/bash"
Paste in this script: (replace MYTARGET with the name of your target (usually same as name of project)

#!/bin/bash
buildNumber=$(/usr/libexec/PlistBuddy -c "Print BuildNumber" MYTARGET/MYTARGET-Info.plist)
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :BuildNumber $buildNumber" MYTARGET/MYTARGET-Info.plist

bundleShortVersion=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" MYTARGET/MYTARGET-Info.plist)
bundleVersion=$bundleShortVersion"."$buildNumber
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $bundleVersion" MYTARGET/MYTARGET-Info.plist

Now, each time you build your app the variable "BuildNumber" will be increased by 1 and your bundle version will be set to Version.BuildNumber. That is, if your app version is 1.0 and your on build number 8, the bundle version will be 1.0.8.

If you, in your app delegate or elsewhere, want to check if the "BuildNumber" has changed since the last run by comparing it with the last value, stored in NSUserDefaults, do something like:

int thisVersion = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"BuildNumber"] intValue];
int lastVersion = [[NSUserDefaults standardUserDefaults] integerForKey:@"lastVersion"];
[[NSUserDefaults standardUserDefaults] setInteger:thisVersion forKey:@"lastVersion"];
BOOL appIsNewVersion = lastVersion != thisVersion;

And there, you have a smooth, automated way of telling if you're on a new build and do the proper initializing, etc.

2012年1月23日月曜日

My first post. I guess it would be a good a idea to share what I do whenever I start a new project in Xcode.

It has kind of become a habit of mine to log to console at almost every method for easy bug tracking. However, using NSLog doesn't show you what method unless you do something like:

NSLog(@"MyViewController:viewDidLoad");

Which is rather cumbersome when you add logging to many methods. This is where my savior comes into the picture.

By adding the following to your "MyApp-Prefix.pch" file in the "Supporting Files"-group

// DLog is almost a drop-in replacement for NSLog
// DLog();
// DLog(@"here");
// DLog(@"value: %d", x);
// Unfortunately this doesn't work DLog(aStringVariable); you have to do this instead DLog(@"%@", aStringVariable);
#ifdef DEBUG
# define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...)
#endif

// ALog always displays output regardless of the DEBUG setting
#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);

you can use ALog (always log) and DLog (debug log) for a rich debug logging.
Simply putting DLog(); in your viewDidAppear method will output something like:

[MainViewController viewDidAppear:] [Line 169]

Even showing the line number where in your source code the line was executed. And don't worry about performance or similar, when compiling for release DLog(); is defined to nothing so there will be no logging going on.

The blog post where I found this information is >>here

First post, logging.

My first post. I guess it would be a good a idea to share what I do whenever I start a new project in Xcode.

It has kind of become a habit of mine to log to console at almost every method for easy bug tracking. However, using NSLog doesn't show you what method unless you do something like:

NSLog(@"MyViewController:viewDidLoad");

Which is rather cumbersome when you add logging to many methods. This is where my savior comes into the picture.

By adding the following to your "MyApp-Prefix.pch" file in the "Supporting Files"-group

// DLog is almost a drop-in replacement for NSLog
// DLog();
// DLog(@"here");
// DLog(@"value: %d", x);
// Unfortunately this doesn't work DLog(aStringVariable); you have to do this instead DLog(@"%@", aStringVariable);
#ifdef DEBUG
# define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...)
#endif

// ALog always displays output regardless of the DEBUG setting
#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);

you can use ALog (always log) and DLog (debug log) for a rich debug logging.
Simply putting DLog(); in your viewDidAppear method will output something like:

[MainViewController viewDidAppear:] [Line 169]

Even showing the line number where in your source code the line was executed. And don't worry about performance or similar, when compiling for release DLog(); is defined to nothing so there will be no logging going on.

The blog post where I found this information is >>here

Related Posts Plugin for WordPress, Blogger...