導入部分の話が面白い
- ムーアの法則
- 昔の PC の話とか
- 昔のネット通信のモデムの音とか
- アーサー・C・クラークの言葉
"Any significantly advanced technology is indistinguishable from magic." "高度に発達したテクノロジーは魔法と見分けがつかない" - 5年前まだ Android がないときにブログに書いた未来の予想が悲観的すぎた、予想よりも早く実現しつつある
- アーサー・C・クラークの言葉その2
"The only way of discovering the limits of the possible is to venture a little way past it into the impossible."
Android Beam の話
# NFC の機能が魔法っぽいってはなしかな
Lockscreen Widget
# Lockscreen Widget って魔法っぽいかな。。。?
"Context isn't important, it's critical."
デバイスはユーザーのことを知っている。デバイスはどんなニュースや本を読んで、どんな音楽を聴いて、どんな映画を見て、どんなゲームをして、だれが友達で、どこで予定があるのか知っている
魔法のような体験を作るための Context を用意する、すごい可能性がここにはある
どんな Context が使える?
例えば Location
Location based Services
Google Play Services の一部として新しい Location based Services をリリースしたよ!
すごい簡単に使えるよ
単にクライアントを作って接続すれば OK
private void connectLBS() {
int gpsExists = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if(gpsExists == ConnectionResult.SUCCESS) {
mLocationClient = new LocationClient(this, this, this);
mLocationClient.connect();
}
}
@Override
public void onConnected(Bundle connectionHint) {
requestUpdates(mLocationClient);
}
どの provider (位置の)が有効かどうかチェックする必要はないGoogle Play Services の一部である Fused Location Provider が最もいい結果を返してくれる
Geofencing
List<Geofence> fenceList = new ArrayList<Geofence>();
// TODO Repeat for all Geofences
Geofence geofence = new Geofence.Builder()
.setRequestId(mKey)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
.setCircularRegion(latitude, longitude, GEOFENCE_RADIUS)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build();
fenceList.add(geofence);
mLocationClient.addGeofences(fenceList, pendingIntent, addGeofenceResultListener);
Activity Recognition
Intent intent = new Intent(this, ActivityRecognitionIntentService.class);
intent.setAction(MyActivity.ACTION_STRING);
PendingIntent pi =
PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mActivityRecognitionClient.requestActivityUpdates(interval, pi);
@Override
protected void onHandleIntent(Intent intent) {
if(intent.getAction() == MyActivity.ACTION_STRING) {
if(ActivityRecognitionResult.hasResult(intent)) {
ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent);
DetectedActivity detectedActivity = result.getMostProbableActivity();
int activityType = detectedActivity.getType();
if (activityType == DetectedActivity.STILL)
setUpdateSpeed(PAUSED);
else if (activityType == DetectedActivity.IN_VEHICLE)
setUpdateSpeed(FASTER);
else
setUpdateSpeed(REGULAR);
}
}
}
Google+ について
友達の状態とかがとれるのでもっと Context にあったことができるという話
(どうなんでしょうね。。。)
知りすぎると Uncanny App Valley に落ちるよ
Google Now は何をするのかはっきりわかるけど、もしソフトキーボードが自分の好きなスポーツを勧めてきたりしたら気持ち悪いよねってこと
Rob Foster の言葉
"Introducing visceral elements into an app.. will make it speak to the subconscious."
人生は単になにをするかだけではなく、何を体験するかだから
カフェで人は単にコップのなかの水を買っているわけではなく。その周りにひろがる体験(雰囲気とか香りとか見た目とかもろもろ)を買っている
アプリでは何ができる?
・テイストはだめだよね
・香り、、、もだめだよね、来年どうなるかみよう
ということで
・見た目
・音
・タッチ
にフォーカスしよう
背後にある哲学、スピリット、ビジョンを理解しよう
→ https://developer.android.com/design をみよう
Text to Speech(TTS)
セットアップは簡単
private void initTextToSpeech() {
Intent intent = new Intent(Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(intent, TTS_DATA_CHECK);
}
@Override
protected void onActivityResult(int request, int result, Intent data) {
if(request == TTS_DATA_CHECK && result == Engine.CHECK_VOICE_DATA_PASS) {
tts = new TextToSpeech(this, new OnInitListener() {
@Override
public void onINit(int status) {
if (status == TextToSpeech.CUSSCESS)
ttsIsInit = true;
}
});
}
else {
startActivity(new Intent(Engine.ACTION_INSTALL_TTS_DATA);
}
}
private void say(String text) {
if (tts != null && ttsIsInit) {
tts.speak(text, TextToSpeech.QUEUE_ADD, null);
}
}
“そのアプリ”を使う理由になる機能をいれよう
Speech Recognition
private void requestVoiceInput() {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
getString(R.string.voice_input_prompt);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE,
Locale.ENGLISH);
startActivityForResult(intent, VOICE_RECOGNITION);
}
@Override
protected void onActivityResult(int request, int result, Intent data) {
if (request == VOICE_RECOGNITION && result == RESULT_OK) {
ArrayList results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
String mostLikelyResult = results[0];
useSpeechInput(mostLikelyResult);
}
}
ここでは、もっともありえそうだとシステムが認識した答え(スタックの一番最初)をつかっているけど、Context を利用して候補のなかからより正しいものを選択するべき
48dip のレイアウト
どれがタッチできるのかユーザーがわかるようにする = ちゃんと state に応じた画像を用意しよう
android:foreground="?android:selectableItemBackground"
Simple Accessibility Support
<Button
...
android:contentDescription="@string/my_button_description"
/>
Custom Control Accessibility Support
public void setHeading(float heading) {
mHeading = heading;
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent e) {
super.dispatchPopulateAccessibilityEvent(e);
String heading = String.valueOf(mHeading);
if(heading.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
heading = heading.subString(0, AccessibilityEvent.MAX_TEXT_LENGTH);
}
event.getText().add("Heading is " + heading + " degree");
return true;
}
ジェスチャーを使う Android Training の Using Touch Gestures クラスがおすすめ
Jazz Hands
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
if (event.getPointerCount() > 1) {
int actionPointerId = action & MotionEvent.ACTION_POINTER_ID_MASK;
int actionEvent = action & MotionEvent.ACTION_MASK;
int pointerIndex = event.findPointerIndex(actionPointerId);
int xPos = (int) event.getX(pointerIndex);
int yPos = (int) event.getY(pointerIndex);
// TODO Magic.
}
}
ルールを破るときは気をつけて、かならずしも positive な反応になるとは限らない
→ Google Play の developer console に追加された ALPHA TESTING, BETA TESTING 機能を使おう
だれがもっともバリューゾーンのユーザーなのかを知ろう、国、言語、デバイス、、、
→ Analytics を使おう
# FFっぽい動画でてきたでw
# Google Quest Cheat Codes...w
スマートフォンの一番の魔法は常にネットに繋がっていることじゃないかな
更新ボタンを eliminate するのはやめよう
ユーザーが更新しようとする前に更新しといてほしいよね
データのやりとりの理由としては
- Client Updates
- Server Updates
- On-Demand Downloads
- Cross-Device Updates
Cross-Device updates にも GCM が使えるよ
キーノートのデモであったように Notification がデバイス間で同期するようになったよ
Google Cloud Messaging: Upstream
GoogleCloudMessaging gcm = GoogleCloudMessaging.get(context);
gcm.send(to, msgId, data);
サーバー側がよくわからない? Mobile Backend Starter を使おう
Client Updates にとてもいいものがあるよ、それが SyncAdapter
public class MySyncAdapter extends AbstractThreadSyncAdapter {
public MySyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) {
super(context, autoInitialize, allowParallelSyncs);
}
public MySyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderCLient provider, SyncResult syncResult) {
// TODO Synchronize your data between client adn server.
}
}
# ネットが繋がっていないと繋がったときに処理をするよう待ってくれたりするらしい
Abstract Account Manager
public class MyAccountAuthenticator extends AbstractAccountAuthenticator {
public static final String ACCOUNT_TYPE = "com.mycompany.myapp";
public static final String ACCOUNT_NAME = "MY STUB ACCOUNT";
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
String authTokenType, String[] requiredFeatures, Bndle options)
throw NetworkErrorException {
AccountManager manager = AccountManager.get(activity);
final Account account = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
manager.addAccountExplicitly(account, null, null);
ContentResolver.setIsSyncable(account, authority, 1);
ContentResolver.setSyncAutomatically(account, authority, true);
return null;
}
...
}
アカウントタイプに気をつける
Account Manager Service
public class MyAuthenticationService extends Service {
MyAccountAuthenticator mAuthenticator;
@Override
public void onCreate() {
mAuthenticator = new MyAccountAuthenticator(this);
}
@Override
public IBinder onBind(Intent intent) {
return mAuthenticator.getIBinder();
}
}
Account Manager XML Config
<account-authenticator xmlns:android="..."
android:accountType="com.mycompany.myapp"
android:icon="@drawable/icon"
android:smallIcon="@drawable/miniicon"
android:label="@string/app_name"
/>
Content Provider
public class MyContentProvider extends ContentProvider {
@Override
public boolean onCreate() { return true; }
@Override
public String getType(Uri uri) { return "vnd.android.cursor.dir/vnd.myapp.items"; }
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sort) { return null; }
@Override
public Uri insert(Uri uri, ContentValues initialValues) { return null; }
@Override
public int delete(Uri uri, String where, String[] whereArgs) { return 0; }
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { return 0; }
}
Manifest
<provider android:authorities="com.mycompany.myapp.myauthority"
android:name=".content_providers.PlaceDetailsContentProvider" />
Sync Adapter XML Config
<sync-adapter xmlns:android="..."
android:contentAuthority="com.mycompany.myapp.myauthority"
android:accountType="com.mycompany.myapp"
android:userVisible="false"
/>
Manifest
<provider android:authorities="com.mycompany.myapp.myauthority"
android:name=".content_providers.PlaceDetailsContentProvider" />
<service android:name=".MyAuthenticationService" android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/accountauth" />
</service>
<service android:name=".MySyncService" android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_myapp" />
</service>
Triggering Syncs
final Account account = new Account(null, MyAccountAuthenticator.ACCOUNT_TYPE);
String authority = "com.mycompany.myapp.myauthority";
mContentResolver.requestSync(account, authority, null);
Periodic Repeating Syncs
final Account account = new Account(null, MyAccountAuthenticator.ACCOUNT_TYPE);
String authority = "com.mycompany.myapp.myauthority";
long interval = 12 * 60 * 60; // 12 hours (24hr by default).
mContentResolver.addPeriodicSync(account, authority, null, interval);
repeating syncs にする理由は、もしサーバーの update が毎日2時にあるとして、そこにアラームをかけてクライアントが同期するようにすると、その時間サーバーにすごい負荷がかかってしまう
時間をベースにして同期はやめよう
たとえネットに接続しなくても次のようにランダムな jitter を使おう
Update Window
final Account account = new Account(null, MyAccountAuthenticator.ACCOUNT_TYPE);
String authority = "com.mycompany.myapp.myauthority";
Random random = new Random();
int jitter = random.nextInt(60);
long start = SystemClock.elapseRealtime() + interval - ((30 + jitter)*60000);
alarmManager.setInexactRepeating(alamType, start, interval, pi);
Acitvity Recognition: Pickup Trigger
@Override
public void onReceive(Context context, Intent intent) {
// Extract the Activity that's been detected.
ActivityDetectionResult activity = ActivityDetectionResult.extractResult(intent);
if(activity != null) {
ActivityType activityType = activity.getMostProvavleActivity().getType();
if (ActivityType.valueOf(activityTypeString) == ActivityType.TILTING)
context.startService(new Intent(context, DailyUpdateService.class);
}
}
On-demand download
SyncAdapter with Calls to Other Updates
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult result) {
downloadServerSideSync();
uploadClientSideSync();
transmitBatchedAnalytics();
executePrefetch();
retryFiledTransfers();
}
# 1時間という長いセッションだったし、最後のほうはコードが多かったな # 3番目の動画がいいね
0 件のコメント:
コメントを投稿