2015/06/23

MortarScopeのためのMock

Mortarを採用したプロジェクトでは各所でMortarScopeが必要になる.
MortarScopeはContext.getSystemServiceを経由して取得されるが, InstrumentationRegistry.getTargetContext()で取得できるContextにはこれが実装されていないためMockオブジェクトを用意する必要がある.

以下はMotar/PresenterをテストするためのMortar/Viewのmockクラス.
MortarScopeを生成するためオリジナルのContextをContext.getSystemServiceのMock Methodを実装したContextWrapperでラップする.

あとはお決まりのEnter/ExitScope処理を実装すればMockは完成する.

private static class Viewer implements MyPresenter.MyViewer {
  private Context context;
  private MortarScope mortarScope;
  private MyPresenter presenter;

  public Viewer(Context context, MyPresenter presenter) {
    // MortarScopeを返すラッパーを定義する. 
    this.context = new ContextWrapper(context) {
      @Override
      public Object getSystemService(String name) {
        return mortarScope != null && mortarScope.hasService(name) ?
            mortarScope.getService(name) : super.getSystemService(name);
      }
    };
    this.presenter = presenter;
  }

  public void onCreate() {
    // お決まりのスコープ定義とEnterScope処理. 
    String scopeName = "UnitTestScope";
    mortarScope = MortarScope.buildRootScope().build("Root").buildChild()
        .withService(BundleServiceRunner.SERVICE_NAME, new BundleServiceRunner())
        .withService(ObjectGraphService.SERVICE_NAME, ObjectGraph.create(new AppHomeModule()))
        .build(scopeName);
    BundleServiceRunner.getBundleServiceRunner(getContext()).onCreate(null);

    presenter.takeView(this);
  }

  public void onDestroy() {
    // お決まりのExitScope処理. 
    presenter.dropView(this);
  }

  // MyPresenter.MyViewerの抽象メソッドを実装
  @Override
  public Context getContext() {
    return context;
  }
}

Mortar/Viewの生成と破棄はsetup/tearDownで実装した.

@Before
public void setup() throws Exception {
  presenter = new MyPresenter();
  viewer = new Viewer(InstrumentationRegistry.getTargetContext(), presenter);
  viewer.onCreate();
}

@After
public void tearDown() {
  viewer.onDestroy();
}

以上.

2015/06/14

RecyclerViewでRealmResultを扱う

Realmはクエリ結果となるRealmResultをListViewで表示するためのアダプタとしてRealmBaseAdapterを用意している.
ListViewを使用する場合は問題ないがRecyclerViewではこれを使用できない.

RealmResultをRecyclerViewで使用するためのアダプタのベースを書いた.
RealmRecyclerViewAdapter.java

import android.content.Context;
import android.support.v7.widget.RecyclerView;

import io.realm.RealmObject;
import io.realm.RealmResults;

public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
    extends RecyclerView.Adapter<VH> {

  protected RealmResults<T> realmResults;
  protected Context context;

  public RealmRecyclerViewAdapter(Context context, RealmResults<T> realmResults) {
    if (context == null) {
      throw new IllegalArgumentException("Context cannot be null");
    }
    if (realmResults == null) {
      throw new IllegalArgumentException("RealmResults cannot be null");
    }

    this.realmResults = realmResults;
    this.context = context;
  }

  @Override
  public int getItemCount() {
    return realmResults.size();
  }
}

RecyclerView.Adapterを実装する際は上記アダプタを継承し, オリジナルのアダプタを作成する.

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import io.realm.RealmResults;

public class ModuleAdapter extends RealmRecyclerViewAdapter<Module, ModuleAdapter.ViewHolder> {

  static class ViewHolder extends RecyclerView.ViewHolder {
    private TextView value, id;

    public ViewHolder(View v) {
      super(v);
      value = (TextView) v.findViewById(android.R.id.text1);
      id = (TextView) v.findViewById(android.R.id.text2);
    }
  }

  public ModuleAdapter(Context context, RealmResults<Module> realmResults) {
    super(context, realmResults);
  }

  @Override
  public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = LayoutInflater.from(context)
        .inflate(android.R.layout.simple_list_item_2, parent, false);
    return new ViewHolder(v);
  }

  @Override
  public void onBindViewHolder(ViewHolder view, int position) {
    Module module = realmResults.get(position);
    view.id.setText(module.getId());
    view.value.setText("value:" + module.getValue());
  }
}

以上.

2015/06/07

Android: Jacoco

apply plugin: 'jacoco'

android {
  jacoco {
    version = '0.7.5.201505241946'
  }

task jacocoTestReport(type: JacocoReport, dependsOn: "connectedCheck") {
  group = "Reporting"
  description = "Generate Jacoco coverage reports"

  reports {
      xml.enabled = false
      html.enabled = true
      html {
          destination "./build/reports/jacoco/${project.name}"
      }
  }

  classDirectories = fileTree(
      dir: './build/intermediates/classes/development/debug',
      excludes: ['**/R.class',
        '**/R$*.class',
        '**/BuildConfig.*',
        '**/Manifest*.*',

        // ButterKnife Gen.
        '**/*$$ViewBinder.*'
      ]
  )

  sourceDirectories = files('src/main/java')
  executionData = files('./build/jacoco/testDevelopmentDebugUnitTest.exec')
}

実行する場合はjacocoTestReportを実行する.

$ ./gradlew :app:jacocoTestReport

excludesの指定について, ButterKnifeライブラリを使用している場合は自動生成されるViewBingerクラスを除外する必要がある.
Jacocoは$記号が連続するクラス名を持つ場合にうまく動作しない. ButterKnifeは$$を含むクラスを生成するため, これを除外しておく必要がある.

参考: Issue 69174: Gradle Build System Jacoco VerifyException when used with Dagger

2015/06/04

Smart Lock for Passwords on Android

端末間でパスワードを同期する

Google Smart Lock for Passwords を使うと、お気に入りのアプリやウェブサイトで使用するアカウントのセキュリティを簡単に強化できます。ユーザーは、Smart Lock で Google アカウントに保存されたパスワードを、Chrome で表示したウェブサイトや Android 搭載端末のアプリで簡単かつ安全に利用できます。

Smart Lock for Passwords を使用すると、Chrome で保存したパスワードを Android 搭載端末で利用できる場合があります。同様に、Android 搭載端末で保存したパスワードを、Google アカウントでログイン済みの Chrome ブラウザで利用できる場合があります。各パスワードは対応するアプリやサイトでのみ利用可能です。詳しくは、ウェブサイトのパスワードを管理するをご覧ください。

携帯端末でパスワードを保存する

Smart Lock for Passwords がオンの場合は、端末のアプリにログインすると、ログイン情報を保存するかどうかの確認を求められる場合があります。[パスワードを保存] をタップすると、アプリ用のパスワードを安全に保存できます。2 つ以上の Google アカウントで端末にログインしている場合は、パスワードを保存するアカウントを選択できます。

Google アカウントに保存されたパスワードで対応アプリに初めてログインすると、Smart Lock for Passwords を使用していることが通知されます。保存したパスワードは passwords.google.com でいつでも確認できます。

引用元: https://support.google.com/accounts/answer/6197437

保存したパスワードを管理する

Chrome ではさまざまなサイトのパスワードを保存できます。Chrome でウェブサイトにログインすると毎回、そのサイトのパスワードを保存するかどうかを確認するメッセージが表示されます。

Chrome にログインしている場合、パスワードは Google アカウントにも同期されます。そのため、同期されたパスワードを別のデバイスでも使用できます。

Chrome でのパスワードの扱い

Chrome でのパスワードの扱いは、デバイス間でパスワードを同期するかどうかによって異なります。

Chrome にログインしているか、Android で Google Smart Lock for Passwords を使っている場合は、Chrome で保存したパスワードを使って Android 搭載端末で自動ログインできる可能性があります。

同様に、Android 搭載端末で保存したパスワードは、Google アカウントでログインした場合に Chrome でも使用できます。

詳しくは、Chrome と Android 搭載端末の間でのパスワードの同期についての説明をご覧ください。

注: Chrome にログインしている場合、ログインに使用した Google アカウントのパスワードを保存するかどうかを確認するメッセージは表示されません。

引用元: https://support.google.com/chrome/answer/95606

Chrome上でWebサービスがID-パスワード形式でユーザ認証する場合, そこで入力したパスワードを保存するかどうか, Chromeが問合せてくる場合がある. パスワードを保存することで同サービスへログインする場合に再度パスワードを入力する必要がなくなるため, サインインの手間を省くことができる.

Chromeのこの便利な機能をAndroidアプリケーション上でも実現することができる.
Smart Lock for Password機構によりアカウントのパスワード情報はGoogleによって安全に管理される.
AndroidアプリケーションはGoogleApiClientを通すことでSmart Lock for Passwordの恩恵を受けることができる.

Smart Lock for Passwords on Android

http://get.google.com/smartlock/

https://developers.google.com/identity/smartlock-passwords/android/

Programmatically save and retrieve credentials, and automatically sign users in across devices and websites in Chrome.

By integrating Smart Lock for Passwords into your Android app, you can automatically sign users in to your app using the credentials they have saved. Users can save both username-password credentials and federated identity provider credentials.

プログラマブルにクレデンシャルを保存および取得し, 異なるデバイス間でのサインインやクロームで訪れるWebサイトの認可処理を自動化する.

Smart Lock for Passwordをあなたのアプリケーションへ統合することで, Smart Lock for Passwordに保存されているクレデンシャルを使用して認可処理を自動化できる. ユーザはユーザ名-パスワードのクレデンシャルと連携IDプロバイダのクレデンシャルを保存することができる.

Integrate Smart Lock for Passwords into your app by using the Credential API to retrieve saved credentials on sign-in. Use successfully retrieved credentials to sign the user in, or use the Credential API to rapidly on-board new users by partially completing your app’s sign in or sign up form. Prompt users after sign-in or sign-up to store their credentials for future automatic authentication.

Smart Lock for Passwordでは保存されたクレデンシャルを取得するためにCredential APIを使う. ユーザのサインインにクレデンシャルを使用するか, Credential APIを使って新しいユーザのサインイン/サインアップを素早く済ませるために一部の情報を埋めた状態でサインイン/サインアップ用の入力フォームを表示する. 自動認可処理のためにユーザがサインイン/サインアップを終えたらクレデンシャルを保存する.

Add Smart Lock for Passwords to Your Android App

Store user credentials with Auth.CredentialsApi.save():

Auth.CredentialApi.save()を使用してクレデンシャルの保存を行う.

Auth.CredentialsApi.save(mCredentialsClient, credential).setResultCallback(
  new ResultCallback() {
      @Override
      public void onResult(Status status) {
          if (status.isSuccess()) {
              // Credentials were saved
          } else {
              if (status.hasResolution()) {
                  // Try to resolve the save request. This will prompt the user if
                  // the credential is new.
                  try {
                      status.startResolutionForResult(this, RC_SAVE);
                  } catch (IntentSender.SendIntentException e) {
                      // Could not resolve the request
                  }
              }
          }
      }
  });

Retrieve stored credentials with Auth.CredentialsApi.request() :

Auth.CredentialsApi.request()で保存されたクレデンシャルを取得する.

Auth.CredentialsApi.request(mCredentialsClient, mCredentialRequest).setResultCallback(
  new ResultCallback() {
      @Override
      public void onResult(CredentialRequestResult credentialRequestResult) {
          if (credentialRequestResult.getStatus().isSuccess()) {
              // Handle successful credential requests
          } else {
              // Handle unsuccessful and incomplete credential requests
          }
      }
  });

Start integrating Smart Lock for Passwords into your Android app

https://developers.google.com/identity/smartlock-passwords/android/get-started

Before you start integrating Smart Lock for Passwords into your app, configure your Google Developers Console project (or create a new project), then enable the Play Services SDK in your Android Studio project. Next steps describes how to integrate Smart Lock for Passwords into your app.

Smart Lock Passwordを導入する前にGoogle Developers Consoleプロジェクトをセットアップし, Android StudioでPlay Service SDKの準備をしておく.

Prerequisites

Smart Lock Password on Androidの必須要件.

  • Android 2.3 or higher かつ Google Play Store 搭載
  • Android emulator with AVD の場合はGoogle APIs platformが搭載された Android 4.2.2 or higher
  • Android Studio(recommended)

Configure your Google Developers Console project

To use Google services, you must create a Google Developers Console project, create an OAuth 2.0 client ID, and register your digitally signed .apk file’s public certificate:

Googleサービスを使用するにはGoogle Developers Console Projectを作成する必要がある. OAuth2.0のクライアントIDを作成し, 電子署名された.apkファイルの公開証明書を登録する.

〜Google Developers Console Projectの説明は省略〜

Configure your Android Studio project

Google Play Serviceを導入. build.gradleのdependenciesを編集する.

compile 'com.google.android.gms:play-services:7.5.0'

AndroidManifest.xmlにGoogle Play Serviceのバージョンを定義.

    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version"/>

Create a GoogleApiClient object

To request stored credentials, you must create an instance of GoogleApiClient configured to access the Credentials API.

保存されたクレデンシャルを得るためにGoogleApiClientをからCredentials APIにアクセスする.

mCredentialsClient = new GoogleApiClient.Builder(this)
    .addConnectionCallbacks(this)
    .addOnConnectionFailedListener(this)
    .addApi(Auth.CREDENTIALS_API)
    .build();

Create a CredentialRequest object

A CredentialRequest object specifies the sign-in systems from which you want to request credentials. Build a CredentialRequest using the setSupportsPasswordLogin() method for password-based sign-in, and the setAccountTypes() method for federated sign-in services such as Google Sign-In.

CredentialRequestオブジェクトはサインインシステムに求めるクレデンシャルのリクエストである. CredentialRequestの構築にはパスワードベースサインインのためのsetSupportsPasswordLogin()と, Google Sing-inのような連携サインインサービスのためのsetAccountTypes()がある.

mCredentialRequest = new CredentialRequest.Builder()
    .setSupportsPasswordLogin(true)
    .setAccountTypes(IdentityProviders.GOOGLE, IdentityProviders.TWITTER)
    .build();

Use the constants defined in IdentityProviders to specify commonly-used sign-in providers. For other sign-in providers, use any string that uniquely identifies the provider. You must use the same provider identifier to store credentials as you use to retrieve the credentials.

IdentityProvidersにある定数を使用すると一般的なサインインプロバイダーを指定できる. 他のサインインプロバイダーはプロバイダーを識別できる他のユニークな文字列IDを使用すること. 保存した時のクレデンシャルと読み込む際のクレデンシャルで使用するプロバイダIDは同じであること.

Request stored credentials

After you have created GoogleApiClient and CredentialRequest objects, pass them to the CredentialsApi.request() method to request credentials stored for your app.

GoogleApiClientCredentialRequestオブジェクトを作成したあとはCredentialsApi.request()メソッドにそれらを渡してアプリに保存されたクレデンシャルをリクエストする.

Auth.CredentialsApi.request(mCredentialsClient, mCredentialRequest).setResultCallback(
    new ResultCallback<CredentialRequestResult>() {
        @Override
        public void onResult(CredentialRequestResult credentialRequestResult) {
            if (credentialRequestResult.getStatus().isSuccess()) {
                // See "Handle successful credential requests"
                onCredentialRetrieved(credentialRequestResult.getCredential());
            } else {
                // See "Handle unsuccessful and incomplete credential requests"
                resolveResult(credentialRequestResult.getStatus(), RC_READ);
            }
        }
    });

Define a callback to handle successful and failed requests using the setResultCallback() method.

コールバックを作成してリクエストの成功と失敗をハンドリングするにはsetResultCallback()メソッドを使用する.

Handle successful credential requests

On a successful credential request, use the resulting Credential object to complete the user’s sign-in to your app. Use the getAccountType() method to determine the type of retrieved credentials, then complete the appropriate sign-in process. For example, for Google Sign-In, create a GoogleApiClient object that includes the user’s ID, then use the object to start the sign-in flow. For password-based sign-in, use the user’s ID and password from the Credential object to complete your app’s sign-in process.

クレデンシャルのリクエストがうまくいくと, サインインの結果としてCredentialオブジェクトを返す. サインインを終えたらgetAccountType()メソッドを使いクレデンシャルの種別を取得する. 例えば, Google Sign-inではユーザIDを含んだGoogleApiClientオブジェクトを作成し, サインインフローを開始する. パスワードベースのサインインではCredentialオブジェクトからユーザIDとパスワードを使用してサインインプロセスを完了させる.

private void onCredentialRetrieved(Credential credential) {
    String accountType = credential.getAccountType();
    if (accountType.equals(IdentityProviders.GOOGLE)) {
        // The user has previously signed in with Google Sign-In. Start the sign-in flow with
        // the same ID.
        // See https://developers.google.com/identity/sign-in/android/
    } else if (accountType == null)
        // Sign the user in with information from the Credential.
        signInWithPassword(credential.getId(), credential.getPassword());
    }
}

Handle unsuccessful and incomplete credential requests

Credential requests can fail when:

  • Multiple credentials are saved
  • No credentials are saved
  • User input is required to proceed
  • There is a network or server error

クレデンシャルリクエストは次のケースで失敗する

  • 複数のクレデンシャルが保存されている
  • クレデンシャルが保存されていない
  • 続けてのユーザ入力が必要
  • ネットワークかサーバのエラー

When multiple credentials are saved or user input is required, the hasResolution() method returns true. In this case, call the status object’s startResolutionForResult() method to prompt the user to choose an account. Then, retrieve the user’s chosen credentials from the activity’s onActivityResult() method by passing Credential.EXTRA_KEY to the getParcelableExtra() method.

複数のクレデンシャルが保存されているか, ユーザの入力が必要である場合, hasResulution()メソッドはtrueを返す. このケースではstartResolutionForResult()メソッドを呼びユーザにアカウントの選択を促す ユーザが選択したクレデンシャルはActivityのonActivityResult()メソッドに渡されるIntentgetParcelableExtra()Credential.EXTRA_KEYを指定することで取得できる.

private void resolveResult(Status status, int requestCode) {
    if (status.hasResolution()) {
        try {
            status.startResolutionForResult(this, requestCode);
        } catch (IntentSender.SendIntentException e) {
            Log.e(TAG, "STATUS: Failed to send resolution.", e);
        }
    } else {
        // The user must create an account or sign in manually.
        Log.e(TAG, "STATUS: Unsuccessful credential request had no resolution.");
    }
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    ...

    if (requestCode == RC_READ) {
        if (resultCode == RESULT_OK) {
            Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
            onCredentialRetrieved(credential);
        } else {
            Log.e(TAG, "Credential Read: NOT OK");
            Toast.makeText(this, "Credential Read Failed", Toast.LENGTH_SHORT).show();
        }
    }

    ...

}

When stored credentials are not found, users must create an account or manually sign in. You can expedite the sign-up and sign-in processes by automatically filling some fields of the forms with information from recently-used accounts. See Provide sign-in hints to a user for information on how to prompt the user to select a recently-used account.

保存されているクレデンシャルが見つからない時, ユーザはアカウントを作成して手動でサインインする必要がある. この時, 最近使用したアカウントから情報を得て入力フォームを埋めることでユーザのサインイン/サインアップを手助けできる. Provide sign-in hints to a user ではどのようにしてユーザに最近使用したユーザを選択させるかの方法が載ってある.

On successful sign in, allow users to save their credentials to automate future authentication on all their devices.

サインインが成功すると, 全てのデバイスで今後サインインを自動化するためにユーザはクレデンシャルを保存することができる.

Provide sign-in hints to a user

Requests to retrieve user credentials can fail when a user has not yet saved credentials or when a user has not yet signed up to your app. In these situations, use the Credentials API to retrieve sign-in hints, such as the user’s name and email address. Use these hints to pre-fill your app’s sign-in and sign-up forms, speeding up your app’s on-boarding process.

ユーザがまだクレデンシャルを保存していない場合やまだアプリケーション上でサインアップしていない場合はクレデンシャルの取得に失敗する可能性がある. この場合, Credentials APIを使用してサインインのヒント(ユーザ名やeメールアドレス etc)を得ることができる. ユーザのサインアップ時にはこれらのヒントを使用してあらかじめサインアップのためのフォームに情報を埋めておき素早くサインアップできるように配慮する.

Retrieve sign-in hints and pre-fill the sign-up form

Sign-in hints are available under the following conditions:

  • CredentialRequestResult.getStatus().isSuccess() is false
  • CredentialRequestResult.getStatus().hasResolution() is true
  • CredentialRequestResult.getStatus().getStatusCode() returns SIGN_IN_REQUIRED

To retrieve the sign-in hints, pass a request code to Status.startResolutionForResult() to indicate that sign-in hints are available:

サインインのヒントは次の条件で使用できる.

  • CredentialRequestResult.getStatus().isSuccess() is false
  • CredentialRequestResult.getStatus().hasResolution() is true
  • CredentialRequestResult.getStatus().getStatusCode() returns SIGN_IN_REQUIRED

サインインのヒントを取得し, これが有効であることを示すためにStatus.startResolutionForResult()へリクエストコードを渡す.

static final int RC_READ = 1;
static final int RC_GET_HINTS = 2;

...

private void resolveResult(Status status) {
    int requestCode;
    if (status.hasResolution()) {
        if (status.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
            requestCode = RC_GET_HINTS;
        } else {
            requestCode = RC_READ;
        }
        try {
            status.startResolutionForResult(MainActivity.this, requestCode);
        } catch (IntentSender.SendIntentException e) {
            Log.e(TAG, "STATUS: Failed to send resolution.", e);
        }
    } else {
        // The user must create an account or sign in manually.
        Log.e(TAG, "STATUS: Unsuccessful credential request had no resolution.");
    }
}

The user is prompted to choose an account to use.

Then, in the activity’s onActivityResult() method, retrieve the hints from the Credential.EXTRA_KEY parcel, check whether the user is in your user database, and start the appropriate activity with the credentials hint.

プロンプトを表示してアカウントを選択するようにユーザへ求める.

ActivityのonActivityResult()メソッドでparcelからCredential.EXTRA_KEYを使ってヒントを取得し, ユーザがあなたのデータベースに存在するかどうか確認し, クレデンシャルヒントを付帯させて適切なActivityを起動する.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RC_GET_HINTS) {
        if (resultCode == RESULT_OK) {
            Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
            Intent intent;
            // Check for the user ID in your user database.
            if (userDatabaseContains(credential.getId())) {
                intent = new Intent(this, SignInActivity.class);
            } else {
                intent = new Intent(this, SignUpNewUserActivity.class);
            }
            intent.putExtra("com.mycompany.myapp.SIGNIN_HINTS", credential);
            startActivity(intent);
        } else {
            Log.e(TAG, "Hint Read: NOT OK");
            Toast.makeText(this, "Hint Read Failed", Toast.LENGTH_SHORT).show();
        }
    }

    ...

}

In the sign-in and sign-up activities, pre-fill the sign-up fields with the sign-in hints that you added to the intent.

サインイン/サインアップのActivityでサインアップ用のフィールドをIntentから得たサインインヒントで予め埋める.

public class SignUpNewUserActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();
        Credential credential = intent.getParcelableExtra("com.mycompany.myapp.SIGNIN_HINTS");

        // Pre-fill sign-up fields
        mUsernameView.setText(credential.getId());
        mDisplaynameView.setText(credential.getName()); // Might be null.

        ...
    }

    ...
}

Store a user’s credentials

After users successfully sign in, create accounts, or change passwords, allow them to store their credentials to automate future authentication in your app.

ユーザのサインインが成功し, アカウントを作成またはパスワードを変更した後, 自動認証のためのクレデンシャルをアプリケーションに保存する.

Store credentials

Create a Credential object containing a user’s sign-in information. For example, to let users store their credentials after successfully signing in with their passwords:

ユーザサインイン情報を含んだCredentialオブジェクトを作成する. 例えばパスワードでログインした後にクレデンシャルを保存する場合は:

Credential credential = new Credential.Builder(email)
        .setPassword(password)
        .build();

Or, for example, after users successfully sign in with their Google accounts:

または, サインインが成功した後にGoogleアカウントを保存する場合.

Credential credential = new Credential.Builder(email)
        .setAccountType(IdentityProviders.GOOGLE)
        .build();

Then, call CredentialsApi.save() to save users’ credentials. If the call to CredentialsApi.save() is not immediately successful, the credentials might be new, in which case the user must confirm the save request. Resolve the save request with startResolutionForResult() to prompt the user for confirmation.

次いで, CredentialsApi.save()を呼び出しクレデンシャルを保存する. CredentialsApi.save()の呼び出しですぐに成功しない場合, 保存しようとしているクレデンシャルが新規作成物であり, ユーザにクレデンシャルの保存の同意を必要としている場合がある.
保存のリクエストを解決するにはstartResolutionForResult()でユーザ確認のプロンプトを表示する.

Auth.CredentialsApi.save(mCredentialsClient, credential).setResultCallback(
            new ResultCallback() {
                @Override
                public void onResult(Status status) {
                    if (status.isSuccess()) {
                        Log.d(TAG, "SAVE: OK");
                        Toast.makeText(this, "Credentials saved", Toast.LENGTH_SHORT).show();
                    } else {
                        if (status.hasResolution()) {
                            // Try to resolve the save request. This will prompt the user if
                            // the credential is new.
                            try {
                                status.startResolutionForResult(this, RC_SAVE);
                            } catch (IntentSender.SendIntentException e) {
                                // Could not resolve the request
                                Log.e(TAG, "STATUS: Failed to send resolution.", e);
                                Toast.makeText(this, "Save failed", Toast.LENGTH_SHORT).show();
                            }
                        } else {
                            // Request has no resolution
                            Toast.makeText(this, "Save failed", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            });
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    ...

    if (requestCode == RC_SAVE) {
        if (resultCode == RESULT_OK) {
            Log.d(TAG, "SAVE: OK");
            Toast.makeText(this, "Credentials saved", Toast.LENGTH_SHORT).show();
        } else {
            Log.e(TAG, "SAVE: Canceled by user", e);
        }
    }

    ...

}

After storing credentials, retrieve them by calling CredentialsApi.request().

クレデンシャルを保存した後, CredentialsApi.request()でそれを取得する.

Enable automatic sign-in across apps and websites

If you have an app and a website that share a user database or use federated sign-in providers such as Google Sign-In, you can associate the app with the website so that users save their credentials once and then automatically sign in to both the app and the website.

アプリとWebサイトを持ち, ユーザデータベースかGoogle Sign-Inのような連携サインインの仕組みを提供する場合, Webサイトとアプリを関連付けてユーザの保存したクレデンシャルをアプリでも使用して自動サインインに利用できる.

To associate an app with a website, first verify that you own the website by using the Google Search Console, then use the Play Developer Console to make the association.

Webサイトとアプリを関連付けるため, 自前WebサイトをGoogle Search Consoleで有効化し, Play Developer Consoleを使ってアプリとWebサイトを関連付ける.

Prerequisites

Your website must be available through HTTPS.

あなたのWebサイトでHTTPSが使える必要がある.

Verify ownership of your website

If you have not already verified ownership of your website with the Google Search Console, do so by following these steps:

まだ有効化されたWebサイトを持っていないのであれば, 次のステップを踏む.

〜Google Search / Play Developer Consoleの操作については省略〜


Sequence

クレデンシャルが保存済みである場合の正常ケース:

Credential saved

サインインが必要, あるいはアカウントの選択が必要である場合の正常ケース:

Need Signin

ソースコードについてはサンプルコードが公開されている.

以上.

2015/06/02

Firebase - セキュリティとFirebase Rules

Firebase公式サイトにあるSecurity&Rulesの翻訳です.
https://www.firebase.com/docs/security/

Security & Rules Quickstart

Firebase provides a flexible, expression-based rules language with JavaScript-like syntax to easily define how your data should be structured and when your data can be read from and written to.
Combined with our login service which allows for easy authentication, you can define who has access to what data and keep all of your user’s personal information secure.
The Security and Firebase Rules live on the Firebase servers and are automatically enforced at all times.

FirebaseはJavaScriptライクな構文でルールを記述できる仕組みを用意しており, データへのRead/Write制御を柔軟で手軽に構築できる.
Firebaseが備えている手軽なログインサービスと組み合わせて, データへのアクセス制御とパーソナルデータをセキュアに保つ.
セキュリティとFirebase RuleはFirebaseサーバ上で動作し, 常に自動でこれらが有効になっている.

Understand the Default Security and Firebase Rules

Security and Firebase Rules are used to determine who has read and write access to your Firebase database as well to ensure the structure of that data. They are found in the Security tab of your Firebase Dashboard. They come in three flavors: .write, .read, and .validate. Here is a quick summary of their purpose:

セキュリティとFirebase Ruleは誰がFirebaseのデータにRead/Writeアクセスできるのかを決定し, またデータ構造を保証する. これらはFirebaseのDashboardにあるSecurityタブから確認できる. これらの情報は.wirte, .read, .validateの3つのキーワードから成る. 下記はそれらキーワード毎の概要である.

Rule Type Description
.read Describes if and when data is allowed to be read by users.
.write Describes if and when data is allowed to be written.
.validate Defines what a correctly formatted value will look like, whether it has child attributes, and the data type.
Rule Type Description
.read ユーザによるデータREADの許容内容を記載する.
.write ユーザによるデータWRITEの許容内容を記載する.
.validate 子要素データの形式やデータタイプのバリデーション記述.

Security and Firebse Rules have a JavaScript-like syntax which makes them easy to work with. By default, your Firebase app has rules which grants every request full read and write permissions to your Firebase:

セキュリティとFirebase RuleはJavaScriptライクなシンタックスで表現される.
デフォルトルールだと, あなたのFirebaseは全ユーザにRead/Writeのフルアクセスを許可している.

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

Security and Firebase Rules live on the Firebase servers and are enforced at all times. Every read and write request will only be completed if your rules allow it. With the default rules above, all requests will be permitted.

セキュリティとFirebase RuleはFirebaseサーバ上で動作し, 常に自動でこれらが有効になっている.
全てのRead/Writeリクエストはあなたの記述したルールで許容されたもののみが実行可能となる. デフォルトルールは前述の通りですべてのリクエストを受け入れる.

SECURITY BEHIND THE SCENES
Firebase handles many other security details for you.
Specifically, we use strong 2048 bit keys for our SSL certificates, sign authentication tokens with SHA256 HMAC signatures, and use BCrypt for password storage.

セキュリティの舞台裏
Firebaseは他にも多数のセキュリティ機能を備えている.
具体的にはSSL証明書には2048bit長鍵を使用しており, 認証トークン署名にはSHA256 HMACシグネチャを用いている. さらにパスワードストレージにはBCryptを使用している.

Use Predefined Variables

There are a number of helpful, predefined variables that can be accessed inside a security rule definition. Here is a brief summary of each:

セキュリティルールを定義する際には予め用意された役に立つ変数を使用することができる.

Variable Description
now The current time in milliseconds since Linux epoch. This works particularly well for validating timestamps created with the SDK’s Firebase.ServerValue.TIMESTAMP.
root A RuleDataSnapshot representing the root path in the Firebase database as it exists before the attempted operation.
newData A RuleDataSnapshot representing the data as it would exist after the attempted operation. It includes the new data being written and existing data.
data A RuleDataSnapshot representing the data as it existed before the attempted operation.
$variables A wildcard path used to represent ids and dynamic child keys. auth Represents an authenticated user’s token payload.
Variable Description
now UNIX標準時ベースの現在時刻(msec). この変数はFirebase.ServerValue.TIMESTAMPのタイムスタンプを検証するのに適している.
root Firebaseデータベースのルートパスを表すRuleDataSnapshot. オペレーションの前に存在するデータを指す.
newData Firebaseデータベースの新しいデータを表すRuleDataSnapshot. オペレーションの後に存在するであろうデータを指す.
data Firebaseデータベースの既存データを表すRuleDataSnapshot. オペレーションの前に存在するデータを指す.
$ variables ID, 子要素のKeyを指すワイルドカードパス
auth 認証済みユーザトークンのペイロード

These variables can be used anywhere in your Security and Firebase Rules. For example, the security rules below ensure that data written to the /foo/ node must be a string less than 100 characters:

これらの変数はセキュリティとFirebase Ruleの各所で使用することができる. 例えば, 下記のセキュリティルールは/foo/ ノードへの書き込みにあたり100文字未満であることをバリデートする.

{
  "rules": {
    "foo": {
      // /foo is readable by the world
      ".read": true,
      // /foo is writable by the world
      ".write": true,
      // data written to /foo must be a string less than 100 characters
      ".validate": "newData.isString() && newData.val().length < 100"
    }
  }
}

Make Your Rules Dynamic

Your Security and Firebase Rules should follow the structure of the data you have stored in your Firebase database. For example, say you are keeping track of a list of messages and that your data looks like this:

セキュリティとFirebase Ruleはあなたが持つFirebase databaseのデータ構造に従ったものとする必要がある.
例えば, メッセージリストをトラッキングするデータ構造は下記のようになる

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "date": 1405704395231
    },
    ...
  }
}

Your rules should be structured in a similar manner. We can make use of $ variables in rules which represent dynamic child keys. In addition, we can use .validate rules to ensure our data is properly structured. For example, the $message variable below represents each of the /messages/ node’s children:

これのルールについても同じ構成である必要がある. ルールの中では$変数を使用して直接の子Keyを表現することができる. また正しい構成であるかの確認に.validateルールも使用できる.
例えば, $message変数は下記の/messages/ノード配下の子要素を表現する.

{
  "rules": {
    "messages": {
      "$message": {
        // only messages from the last ten minutes can be read
        ".read": "data.child('timestamp').val() > (now - 600000)",
        // new messages must have a string content and a number timestamp
        ".validate": "newData.hasChildren(['content', 'timestamp']) && newData.child('content').isString() && newData.child('timestamp').isNumber()"
      }
    }
  }
}

Integrate Authentication Rules

Firebase gives you full control over user authentication. Login providers are server-side components that authenticate your users. Choose a built-in login provider for a common authentication use case, or build your own custom login provider to address special login needs.

Firebaseはユーザ認証の古コントロール機能を提供する. サーバサイドコンポーネントとしてユーザ認証を行うログインプロバイダーを提供する. 共通の認証にビルトインのログインプロバイダーを選択するか, 特別なログインの必要性を満たすためにカスタムされたログインプロバイダーを選択できる.

No matter how you authenticate your user, this action defines the auth variable in your Security and Firebase rules. This variable contains the user’s auth payload, which includes that user’s unique identifier (uid), and the name of the provider they logged with. Built-in providers also add provider-specific fields to auth, such as the user’s name. If you implement a custom login provider, you can add your own fields to your user’s auth payload.

あなたが選択した認証方法に関わらず, アクションはセキュリティとFirebase Ruleの中でauth変数として定義される. この変数はユーザ認証のペイロードを含み, ユーザを一意に識別(uid)し, プロバイダーの名前が記録される. ビルトインプロバイダーはユーザの名前などプロバイダー固有の領域をauthへ追加する.

Typically, you’ll store all of your users in a single users node whose children are the uid values for every user. If you wanted to restrict access to this data such that only the logged-in user can see their own data, your rules would look something like this:

一般的に, 全てのユーザを単一のusersノードにまとめ, 各ユーザはuidの値として保持する. もしログインしたユーザにのみデータへのアクセスを制限したい場合は次のようなルールになる.

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid"
      }
    }
  }
}
2015/06/01

RecyclerView ItemTouchHelper

はじめに

ItemTouchHelper - Android Developers

Android support libraryのRecyclerViewにItemTouchHelperが追加された. support library v22.2.0を導入することで使用できる.

compile 'com.android.support:recyclerview-v7:22.2.0'

ItemTouchHelperを使用すればアイテム項目のスワイプやドラッグ&ドロップ操作を簡単に導入できる.

    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
    recyclerView.setHasFixedSize(false);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    adapter = new RecyclerViewAdapter();
    recyclerView.setAdapter(adapter);
    ItemTouchHelper itemDecor = new ItemTouchHelper(
        new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
            ItemTouchHelper.RIGHT) {
          @Override
          public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
            final int fromPos = viewHolder.getAdapterPosition();
            final int toPos = target.getAdapterPosition();
            adapter.notifyItemMoved(fromPos, toPos);
            return true;
          }

          @Override
          public void onSwiped(ViewHolder viewHolder, int direction) {
            final int fromPos = viewHolder.getAdapterPosition();
            datasource.remove(fromPos);
            adapter.notifyItemRemoved(fromPos);
          }
        });
    itemDecor.attachToRecyclerView(recyclerView);

ソースコードはGitHubに公開している.

関連するクラスとメソッドを下記する.

ItemDecoration

android.support.v7.widget.RecyclerView.ItemDecoration

Class Overview

An ItemDecoration allows the application to add a special drawing and layout offset to specific
item views from the adapter’s data set. This can be useful for drawing dividers between items,
highlights, visual grouping boundaries and more.

ItemDecorationはRecyclerViewのAdapterが持つデータセットに対する特殊な描画やレイアウトといった装飾効果を与えるものである. リストアイテムの区切り線やハイライト, グルーピング表示といったことによく使用される.

All ItemDecorations are drawn in the order they were added, before the item views (in onDraw() and
after the items (in onDrawOver(Canvas, RecyclerView, RecyclerView.State)).

アイテム項目の装飾は項目を描画する前に(onDraw(Canvas, RecyclerView, RecyclerView.State))が呼ばれ, 項目が追加された後に(onDrawOver(Canvas, RecyclerView, RecyclerView.State))が呼ばれる.

Method

public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state)

Draw any appropriate decorations into the Canvas supplied to the RecyclerView. Any content drawn by this method will be drawn before the item views are drawn, and will thus appear underneath the views.

RecyclerViewのCanvasに装飾を行う. このメソッドはアイテム項目が描画される前に呼ばれる. そのため, アイテムビュよりも背面へ描画されることになる.

public void onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state)

Draw any appropriate decorations into the Canvas supplied to the RecyclerView. Any content drawn by this method will be drawn after the item views are drawn and will thus appear over the views.

RecyclerViewのCanvasに装飾を行う. このメソッドはアイテム項目が描画された後に呼ばれる. そのため, アイテムビュよりも前面へ描画されることになる.


ItemTouchHelper

android.support.v7.widget.helper.ItemTouchHelper
extends RecyclerView.ItemDecoration
implements RecyclerView.OnChildAttachStateChangeListener

Class Overview

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.

It works with a RecyclerView and a Callback class, which configures what type of interactions
are enabled and also receives events when user performs these actions.

ItemTouchHelperはRecyclerView Itemのswipe to dismissとdrag & dropをサポートするユーティリティである.

Depending on which functionality you support, you should override
onMove(RecyclerView, ViewHolder, ViewHolder) and / or onSwiped(ViewHolder, int).

This class is designed to work with any LayoutManager but for certain situations,
it can be optimized for your custom LayoutManager by extending methods in the
ItemTouchHelper.Callback class or implementing ItemTouchHelper.ViewDropHandler
interface in your LayoutManager.

どちらの機能をサポートするかで, onMove(RecyclerView, ViewHolder, ViewHolder), onSwiped(ViewHolder, int)のどちらか, あるいは両方をoverrideする.

このクラスはいくつかのLayoutManagerと協調し, 特定の状況下でうまく動作するようにデザインされている. カスタムLayoutManagerを作成する場合はItemTouchHelper.Callbackを拡張して最適化するかItemTouchHelper.ViewDropHandlerインタフェースをカスタムLayoutManagerに実装するかする.

By default, ItemTouchHelper moves the items’ translateX/Y properties to reposition them.
On platforms older than Honeycomb, ItemTouchHelper uses canvas translations and View’s
visibility property to move items in response to touch events.
You can customize these behaviors by overriding onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int, boolean) or onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean).

Most of the time, you only need to override onChildDraw but due to limitations of platform prior
to Honeycomb, you may need to implement onChildDrawOver as well.

標準ではItemTouchHelperはtranslateX/Yプロパティでアイテム項目を移動させる. Honeycombより古いプラットフォームではcanvasのtranslationとViewのvisibilityプロパティでアイテム項目を移動させる.
これらの振る舞いはonChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)かonChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)をオーバライドすることでカスタマイズできる.

多くの場合, onChildDrawのオーバライドのみが必要であるが, Honeycombより古いプラットフォームではonChildDrawOverの実装を必要とする場合がある.


ItemTouchHelper.Callback

android.support.v7.widget.helper.ItemTouchHelper.Callback

Class Overview

This class is the contract between ItemTouchHelper and your application. It lets you control which touch
behaviors are enabled per each ViewHolder and also receive callbacks when user performs these actions.

このクラスはあなたのアプリケーションとItemTouchHelperの間をとりもつクラスである. タップが有効なViewHolder毎の振る舞いを制御し, ユーザがそれらのアクションを起こした時のコールバックを受け取る.

To control which actions user can take on each view, you should override getMovementFlags(RecyclerView, ViewHolder) and return appropriate set of direction flags. (LEFT, RIGHT, START, END, UP, DOWN).
You can use makeMovementFlags(int, int) to easily construct it. Alternatively, you can use ItemTouchHelper.SimpleCallback.

ユーザがリスト項目に対してとれるアクションをコントロールするにはgetMovementFlags(RecyclerView, ViewHolder)メソッドをオーバーライドし, コントロール可能な方向を示すフラグ(LEFT, RIGHT, START, END, UP, DOWN)を返却する.
方向を示すフラグを簡単に作成するためにmakeMovementFlags(int, int)メソッドも用意されている.
あるいはItemTouchHelper.SimpleCallbackを使うこともできる.

If user drags an item, ItemTouchHelper will call onMove(recyclerView, dragged, target).
Upon receiving this callback, you should move the item from the old position (dragged.getAdapterPosition())
to new position (target.getAdapterPosition()) in your adapter and also call notifyItemMoved(int, int).

もし, ユーザがリスト項目をドラッグしたならば, ItemTouchHelperはonMove(recyclerView, dragged, target)を呼ぶ.
このコールバックう受け取った時, 古いポジション (dragged.getAdapterPosition())にあるリスト項目は新しいポジション(target.getAdapterPosition())に移動させ, それが終わるとnotifyItemMoved(int, int)を呼び出す必要がある.

When a dragging View overlaps multiple other views, Callback chooses the closest View with which dragged View might have changed positions.
Although this approach works for many use cases, if you have a custom LayoutManager, you can override
chooseDropTarget(ViewHolder, java.util.List, int, int) to select a custom drop target.

ドラッグ中のViewが他の複数のViewと重なった場合, コールバックにはより近くにあるViewのポジションがドロップ先のポジションとして渡される. このアプローチは多くのユースケースで有効に働くが, カスタムLayoutManagerを使用しており, これをカスタマイズしたい場合はchooseDropTarget(ViewHolder, java.util.List, int, int) でドロップ対象を変更することができる.

When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls
onSwiped(ViewHolder, int). At this point, you should update your adapter (e.g. remove the item) and
call related Adapter#notify event.

Viewがスワイプされた時, ItemTouchHelperは画面外に追い出すアニメーション効果をもたらす. この時onSwiped(ViewHolder, int)のコールバックが呼ばれる. この時点でRecyclerViewのAdapterを更新(例えば削除)し, Adapterのnotifyイベントを呼び出す必要がある.


ItemTouchHelper.SimpleCallback

android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback

Class Overview

A simple wrapper to the default Callback which you can construct with drag and swipe directions and
this class will handle the flag callbacks. You should still override onMove or onSwiped depending on your use case.

ItemTouchHelper.Callbackのシンプルなラッパー. 指定されたフラグ(LEFT, RIGHT, START, END, UP, DOWN)に対するスワイプとドラッグのコールバックをハンドリングする. あなたのユースケースにあわせてonMoveとonSwipedをオーバライドする.

ItemTouchHelper mIth = new ItemTouchHelper(
     new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
         ItemTouchHelper.LEFT) {
         public abstract boolean onMove(RecyclerView recyclerView,
             ViewHolder viewHolder, ViewHolder target) {
             final int fromPos = viewHolder.getAdapterPosition();
             final int toPos = viewHolder.getAdapterPosition();
             // move item in `fromPos` to `toPos` in adapter.
             return true;// true if moved, false otherwise
         }
         public void onSwiped(ViewHolder viewHolder, int direction) {
             // remove from adapter
         }
 });