javascriptからAndroidを呼び出す/Androidからjavascriptを呼び出す

javascriptAndroid

javascript interfaceを用意

適当なjavaオブジェクトでおっけー
今回はToastを表示するオブジェクト作りました

import android.content.Context;
import android.widget.Toast;

public class Toaster {

  private Context context;

  public Toaster(Context context) {
    this.context = context;
  }

  public void show(String text) {
    Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
  }
}
WebViewにjavascript interfaceを追加する

WebViewのインスタンスを取得したらさっき作ったオブジェクトを追加します

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  // WebView取得
  browser = (WebView) findViewById(R.id.browser);

  // javascript有効化
  browser.getSettings().setJavaScriptEnabled(true);

  // Toastを表示するjavascript interfaceを追加
  // 第一引数がオブジェクトで、第二引数がjavascriptから呼び出すときの名前
  browser.addJavascriptInterface(new Toaster(this), Toaster.class.getSimpleName());

  // assetのindex.htmlを読み込み
  browser.loadUrl("file:///android_asset/index.html");
}
javascriptから呼び出す

あとはこんな感じで呼び出せます

<input type="button" value="click me" onclick="Toaster.show('Hello, World!')" />
実行イメージ


ボタンを押すと…

Toastが表示されました!

Androidjavascript

html側に実行するjavascriptを用意

javascriptの関数書きます
抜粋するとこんな感じ

<head>
<script type="text/javascript">
function change_message(message){
  document.getElementById('message').innerHTML = message;
}
</script>
</head>
<body>
<p id="message"></p>
</body>

alert()でもよかったんですが、android-webviewのalertは表示部分がJavaになって分かりづらいので
今回はテキストを変更する関数を書きました。

Androidから呼び出す

とっても簡単、loadUrlにjavascript書くだけ

browser.loadUrl("javascript:change_message('HogeHoge')");
実行イメージ


メニューを押すと

テキストが変わりました!

Screen Capture Shortcutをリリースしました

リリースしました

新しいアプリをリリースしました。
このアプリを使うと画面のキャプチャを撮ることができます。
しかし動作するのはGalaxyS/GalaxyTabのみです。

無料版


有料版


残念な仕様

Galaxyシリーズにはスクリーンキャプチャを撮るための仕組みがついています。
GalaxySであれば戻るボタンを押しながらホームボタンを、
GalaxyTabであれば戻るボタンを押しながら電源ボタンを押せばスクリーンキャプチャが撮れます。
どちらも戻るボタンを押さねばなりません。
そこでいざスクリーンキャプチャを撮ろうとすると今の画面が取りたいのに前の画面へ戻ってしまいます。
戻る押してるんだから当然ですね…。

そこで

戻るボタンとかホームボタンとか押さなくてもスクリーンキャプチャを呼び出せるアプリを作りました!
使用方法は2通りです。
・notificationバーから呼び出す
・端末を振る(有料版のみ)

これで

いろんな画面を簡単にキャプチャーできますねー。

Launcherからアプリを消したい

だれか助けてください

今書いているアプリでどうしても必要な機能なのですが、どうにも上手くうごきません…、だれか助けてください…

Launcherから消し去りたい

アプリのLauncher表示の切り替えをしたくてPackageManager#setComponentEnabledSettingを使い切り替えるコードを書きました。
PackageManager#setComponentEnabledSettingについてはこちら

Taosoftware: Android Intent呼び出しを自分でコントロール方法
http://www.taosoftware.co.jp/blog/2010/04/android_intent.html

taosoftwareさんではACTION_VIEW/CATEGORY_BROWSABLEのActivityの切り替えをしていますが、ACTION_MAIN/CATEGORY_LAUNCHERのActivityの切り替えをしたらLauncherからの非表示ができるのではないかと考えたわけです。

検証用のコードはこちら

SettingActivity
import android.content.ComponentName;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;

public class SettingActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.setting);

    int state = getPackageManager().getComponentEnabledSetting(new ComponentName(this, SettingActivity.class));
    if (state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
      PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("visible_in_launcher_1", true);
    } else {
      PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("visible_in_launcher_1", false);
    }

    state = getPackageManager().getComponentEnabledSetting(new ComponentName(this, SettingActivity.class));
    if (state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
      PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("visible_in_launcher_2", true);
    } else {
      PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("visible_in_launcher_2", false);
    }
  }

  @Override
  protected void onResume() {
    super.onResume();
    PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
  }

  @Override
  protected void onPause() {
    super.onPause();
    PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    int newState;
    if (sharedPreferences.getBoolean(key, true)) {
      newState = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
    } else {
      newState = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
    }
    String packageName = getPackageName();
    ComponentName componentName;
    if (key.equals("visible_in_launcher_1")) {
      componentName = new ComponentName(packageName, packageName + ".SettingActivity");
    } else {
      componentName = new ComponentName(packageName, packageName + ".SettingActivityAlias");
    }
    PackageManager packageManager = getPackageManager();
    packageManager.setComponentEnabledSetting(componentName, newState, PackageManager.DONT_KILL_APP);
  }
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="jp.tomorrowkey.android.visibleinlauncherapp"
  android:versionCode="1"
  android:versionName="1.0">
  <application
    android:icon="@drawable/icon"
    android:label="@string/app_name">
    <activity
      android:name=".SettingActivity"
      android:label="App1"
      android:enabled="true">
      <intent-filter>
        <action
          android:name="android.intent.action.MAIN" />
        <category
          android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity-alias
      android:name=".SettingActivityAlias"
      android:targetActivity=".SettingActivity"
      android:label="App2"
      android:enabled="true">
      <intent-filter>
        <action
          android:name="android.intent.action.MAIN" />
        <category
          android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity-alias>
    <receiver
      android:name=".PackageChangedReceiver">
      <intent-filter>
        <action
          android:name="android.intent.action.PACKAGE_CHANGED" />
        <data
          android:scheme="package" />
      </intent-filter>
    </receiver>
  </application>
  <uses-sdk
    android:minSdkVersion="3" />
</manifest> 

ちょっとわけあってsetComponentEnabledSettingでenableを切り替えるActivityを2つにしています。
また、setComponentEnabledSettingを使って変更をするとPACKAGE_CHANGEDがブロードキャストされるので、ブロードキャストされたことを分かりやすく見せるためにToastを表示するようにしています。

package jp.tomorrowkey.android.visibleinlauncherapp;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class PackageChangedReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    Toast.makeText(context, "receive package changed", Toast.LENGTH_SHORT).show();
  }
}

動かない…

まず、Launcherの状態がこちら

アプリを起動するとこんな画面が表示されます。

1のチェックボックスを外すと

PACKAGE_CHANGEDが受信され

LauncherからApp1のリンクが消えます。

思ったように動きます。

ここからが問題です。
この状態でApp2のチェックボックスを外すと…

PACKAGE_CHANGEDが受信され

Launcherから消えない…

なぜか消えません…
ちなみに起動しようとしてみると

「アプリがインストールされてません」って言われます…
どうも、最後の1つは消えてくれないようです。

検証Homeアプリ
  • GalaxyS純正Launcher
    • 消えない
  • HT-03A純正Launcher
    • 消えない
  • ADWLauncher
    • 消えない
  • Xperia(1.6)純正Launcher
    • 消える
  • Xperia mini pro(1.6)純正Launcher
    • 1つも消えない

たすけてください

どうにかしてLauncherから消す方法はないでしょうか…。
知っている方おしえてください…

ある程度時間が経過したらプログレスダイアログを表示する

最初からプログレスを表示せずにある程度時間が経ったらプログレスダイアログを表示します。
処理時間がまちまちな時に使えるんじゃないかなと思います。
onPostExecuteでプログレス非表示/メッセージキャンセルのif文がこんなので大丈夫か不安です。
初めてメッセージ使った。

AsyncTaskソース

処理が2秒以上掛かる場合はプログレスを表示します。

class WaitTask extends AsyncTask<Integer, Void, Integer> {

  /* プログレスが表示されるまでの閾値 */
  private static final int PROGRESS_DELAY = 2000;

  /* Message識別*/
  private final int MESSAGE_WHAT = 100;

  private Context context;
  private ProgressDialog progressDialog = null;
  private Handler handler;

  public WaitTask(Context context) {
    this.context = context;
    handler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
        progressDialog = new ProgressDialog(WaitTask.this.context);
        progressDialog.setMessage("please wait");
        progressDialog.show();
      }
    };
  }

  @Override
  protected Integer doInBackground(Integer... params) {

    Message msg = new Message();
    msg.what = MESSAGE_WHAT;
    handler.sendMessageDelayed(msg, PROGRESS_DELAY);

    try {
      //何か時間が掛かる処理
      Thread.sleep(params[0]);
    } catch (InterruptedException e) {
      Log.d("DelayedProgress", e.getMessage(), e);
    }
    return params[0];
  }

  @Override
  protected void onPostExecute(Integer result) {
    if (progressDialog != null && progressDialog.isShowing()) {
      progressDialog.dismiss();
    } else {
      handler.removeMessages(MESSAGE_WHAT);
    }
    Toast.makeText(context, String.format("%d second has passed", result), Toast.LENGTH_LONG).show();
  }
}

ドット絵を描くアプリを作ったよ!

ドット絵を描くアプリを作ったよ

.Picというアプリを作りました!
このアプリを使うとデコメを自作することができます。


DotPicFree


無料です。
使える色が少ないです。

DotPic


300円です。
色が自由に選べます。

無料アプリと有料アプリのプロジェクトを管理する

アプリを作った時に無料アプリと有料アプリと2バージョン作る事ってよくあると思います。
無料アプリと有料アプリに分けたい場合、パッケージ名を変えないといけないのでプロジェクトを2つ立てる必要があります。
その場合、ソースが2重管理になってしまい、リリース後の改修が非常にめんどくださいです。
これはデコ美のバージョンアップでだいぶ苦しめられました…。
これを楽にしようと考えて方法を考えました。

プロジェクト作る

Androidライブラリを使うとソースが1つになり、管理が楽になります。
プロジェクトは合計3つ作ります。

  • AndroidPayApp(Androidアプリ)
    • 有料アプリ
    • package:jp.tomorrowkey.android.androidpayapp
  • AndroidFreeApp(Androidアプリ)
    • 無料アプリ
    • package:jp.tomorrowkey.android.androidfreeapp
  • AndroidAppCore(Androidライブラリ)
    • 実装コード
    • package:jp.tomorrowkey.android.androidcoreapp

方針

AndroidAppCodeにすべてのコードを書きます。
AndroidPayAppとAndroidFreeAppの2つのアプリはAndroidAppCoreを参照します。
AndroidPayAppとAndroidFreeAppの2つのプロジェクトには一切ソースは置きません。

AndroidManifest.xml

AndroidPayAppとAndroidFreeAppのAndroidManifestにはAndroidAppCoreのActivityの定義を書きます。
今回は1つのActivityのみなので以下のようになります。

<?xml version="1.0" encoding="utf-8"?>
<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="jp.tomorrowkey.android.androidpayapp"
  android:versionCode="1"
  android:versionName="1.0">
  <application
    android:icon="@drawable/icon"
    android:label="@string/app_name">
    <activity android:name="jp.tomorrowkey.android.androidlib.MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
  <uses-sdk android:minSdkVersion="4" />
</manifest> 

掲載したのはAndroidPayAppのAndroidManifestです。
AndroidFreeAppのAndroidManifestはほぼ一緒なので載せません。

これでアプリからライブラリに定義されているActivityが呼び出せます。

無料アプリと有料アプリの判断

無料アプリは機能に制限をもたせないといけないので、無料アプリと有料アプリの判断をするコードが必要です。
Context#getPackageName()で有料アプリ、無料アプリそれぞれのパッケージ名が返ってくるのでそれで判断します。
Utilクラスでも作っておきましょう。

import android.content.Context;

public class Util {
  private static final String PAY_APP_PACKAGE_NAME = "jp.tomorrowkey.android.androidpayapp";
  public static boolean isPaymentApp(Context context) {
    String packageName = context.getPackageName();
    return PAY_APP_PACKAGE_NAME.equals(packageName);
  }
}

動作


アイコンとアプリ名はAndroidManifestにそれぞれ定義するので
無料アプリと有料アプリで別のものを使用することができます。
今回は無料アプリにグレーのアイコンを、有料アプリに緑色のアイコンを使いました。


それぞれのアプリを実行しました。
パッケージ名から無料版・有料版の判断をしてそれぞれの文字列を出力しています。

これで

かなり管理が楽に!
もう何も考えなくていいよう!

Androidでカラーピッカーを作ろう

HSVって?

HSV色空間 - Wikipedia 
http://ja.wikipedia.org/wiki/HSV%E8%89%B2%E7%A9%BA%E9%96%93
  • Hue - 色相
  • Saturation - 彩度
  • Value - 明度

の3つで表す色の指定方法です。
RGBの親戚みたいなものです。

wikipedia読むとなにやら難しい事書いてあります。
ぼくはあんまりよんでません。

カラーピッカーを作りたい

wikipediaに書いてある事をぜんぶ読まなくても
AndroidにはHSV⇔RGBのメソッドが用意されているんで読まなくてもいいです。
理解するのは値の範囲くらい

色相 0〜360
彩度 0%〜100%
明度 0%〜100%

使うメソッドはColor#HSVToColor(float[])てやつ

Color | Android Developers 
http://developer.android.com/reference/android/graphics/Color.html#HSVToColor(float[])

配列にHSVの値を入れたらRGBを返してくれるメソッドです。

色相バーを作ろう

色相の値を1ずつずらして色を並べれば色相バーが作れます。

コード
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Bitmap.Config;
import android.os.Bundle;
import android.widget.ImageView;

public class MainActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // 画像を作る
    int height = 60;
    int width = 360;
    Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
    int color;

    // 色を設定する
    for (int i = 0; i < 360; i++) {
      // 色相に対応する色を取得する
      color = Color.HSVToColor(new float[] { i, 1, 1 });
      // 画像に色を設定する
      for (int j = 0; j < height; j++) {
        bitmap.setPixel(i, j, color);
      }
    }

    // イメージをActivityに表示する
    ImageView imageView = new ImageView(this);
    imageView.setImageBitmap(bitmap);
    setContentView(imageView);
  }
}
実行結果

彩度や明度

色相とほぼ同じコードで作れると思います。
がんばって!

カラーピッカー

私はこんな感じのカラーピッカー作りました。

ソースはこちら
http://tomorrowkey.googlecode.com/svn/trunk/ColorPallet
すぐ動くサンプルはこちら
http://tomorrowkey.googlecode.com/svn/trunk/ColorPallet/bin/ColorPallet.apk
署名してません。