くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

システムアーキテクト試験の過去問一括ダウンロードスクリプト

先日IPAシステムアーキテクト試験を受けてきた。その際、過去問を一括ダウンロードするために書いたスクリプト
アプリケーションエンジニア試験時代(平成16年~平成20年)は、午前がまとまっているので、午後の問題のみ。

#!/bin/bash

# *****************************************************************************
# * システムアーキテクト試験の過去問一括ダウンロードスクリプト
# *****************************************************************************

first_year=16 # 平成16年から
last_year=29  # 平成29年まで

base_url='https://www.jitec.ipa.go.jp/1_04hanni_sukiru'
dl_dir='dist'

dl_pdf() {
  year_label="${1}h${2}"
  fname="$3"

  url="${base_url}/mondai_kaitou_${year_label}_2/${year_label}a_${fname}.pdf"
  echo "#### DOWNLOAD: ${url}"

  wget ${url} -P "./${dl_dir}"
  sleep 1  # 1秒待つ
}

rm -rf ${dl_dir}
mkdir ${dl_dir}

for i in `seq ${first_year} ${last_year}`;do
  year="$((1988 + i))"
  year2="${i}"
  echo "${year} - ${year2}"
  if [ $year2 -gt 21 ]; then
    dl_pdf "$year" "$year2" 'sa_am2_qs'
    dl_pdf "$year" "$year2" 'sa_am2_ans'
    dl_pdf "$year" "$year2" 'sa_pm1_qs'
    dl_pdf "$year" "$year2" 'sa_pm1_ans'
    dl_pdf "$year" "$year2" 'sa_pm1_cmnt'
    dl_pdf "$year" "$year2" 'sa_pm2_qs'
    dl_pdf "$year" "$year2" 'sa_pm2_ans'
    dl_pdf "$year" "$year2" 'sa_pm2_cmnt'
  else
    dl_pdf "$year" "$year2" 'ae_pm1_qs'
    dl_pdf "$year" "$year2" 'ae_pm1_ans'
    dl_pdf "$year" "$year2" 'ae_pm1_cmnt'
    dl_pdf "$year" "$year2" 'ae_pm2_qs'
    dl_pdf "$year" "$year2" 'ae_pm2_ans'
    dl_pdf "$year" "$year2" 'ae_pm2_cmnt'
  fi
done

参考にしたサイト様

【Android】パーミッション無しで画像のURIを取得する

別のActivityをファイル選択ダイアログ的に使う方法。
よくやるけど忘れるやつ。よく忘れるのでφ(..)メモメモ

画像選択するActivityを開く

int RESULT_PICK_IMAGEFILE = 1000;

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, RESULT_PICK_IMAGEFILE);

選択した画像のURIを受け取る

@Override
public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {
    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        if (resultData.getData() != null) {
            Uri uri = resultData.getData();
            // ...
        }
    }
}

参考にしたサイト様

Django Admin Siteが便利すぎてつらい

admin-site便利。

以下、公式サイトより。

Djangoのパワフルな特徴の1つは、自動的に生成されるadminインタフェースです。あなたのモデルクラスからメタデータを読み取り、モデル中心のインタフェースを提供します。このインタフェースのおかげで、ユーザはあなたのサイトのコンテンツを操作することができます。adminサイトのオススメの使い方は、組織内で利用する管理ツールに利用を制限することです。adminサイトは、あなたのサイトのフロントエンドやその周辺を含んだ全体を作成することを意図していません。

  • Modelを作って、
  • Admin Siteに登録すると、
  • Modelの参照・更新・削除の画面を自動生成してくれる 管理画面用のサイト。これがデフォルトの機能なのがすごい。。

urls.py

python manage.py startprojectの雛形で以下のような感じになっている

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

admin.py

python manage.py startappの雛形でadmin.pyがあるので、そこにいろいろ設定 ちなみに、models.pyはこんな感じ。

# models.py
from django.db import models
from django.utils.translation import ugettext_lazy

class Todo(models.Model):
  title = models.CharField('タイトル', max_length=20)
  done = models.BooleanField('完了', default=False)
  
  def __str__(self):
    return "{}".format(self.title)

  class Meta: # 表示クラス名を変更する
    verbose_name = ugettext_lazy("TODO")
    verbose_name_plural = ugettext_lazy("TODO")

admin.ModelAdminを継承したクラスを作って、もろもろを設定

# admin.py
from django.contrib import admin
from .models import Todo

@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
  ordering = ['id'] # idの昇順でソート
  list_display = ('id', 'name') # リストで表示するフィールド

小ネタ

ManyToManyFieldを連結した表示にしたい

# models.py
from django.db import models
from django.utils.translation import ugettext_lazy

class Category(models.Model):
  name = models.CharField('カテゴリ名', max_length=20)

class Todo(models.Model):
  title = models.CharField('タイトル', max_length=20)
  done = models.BooleanField('完了', default=False)
  categories = models.ManyToManyField(Category, verbose_name='カテゴリ')
# admin.py
from django.contrib import admin
from .models import Todo

@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
  ordering = ['id'] # idの昇順でソート
  list_display = ('id', 'name', 'category_list') # リストで表示するフィールド
  
  # list_displayの独自カラムは関数に置き換えれるので、よい感じの表示に変更
  def category_list(self, obj):
      return ", ".join([cat.name for cat in obj.categories.all()])

ImageFieldを画像で表示したい

# models.py
from django.db import models

class Todo(models.Model):
  title = models.CharField('タイトル', max_length=20)
  done = models.BooleanField('完了', default=False)
  image = models.ImageField()
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import Todo

@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
  ordering = ['id'] # idの昇順でソート
  list_display = ('id', 'name', 'thumbnail') # リストで表示するフィールド
  
  # list_displayの独自カラムは関数に置き換えれるので、よい感じの表示に変更
  def thumbnail(self, obj):
      if obj.image and hasattr(obj.image, 'url'):
          return format_html('<img src="{}" width="50" height="50" />', obj.image.url)
      else:
          return 'No Image'

Apacheでデプロイする

設定せずにデプロイすると、adminサイトのcssが読み込まれない。。

1. settings.pyに以下を追加。
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static") # プロジェクトルート直下のstaticディレクトリ
2. 利用されているcssをプロジェクトルート直下のstaticディレクトリに配置
$ python manage.py collectstatic
3. /etc/httpd/con.d/の設定にプロジェクトルート直下のstaticディレクトリを参照するようAlias
Alias /static/ /var/www/my_project/my_app/static/
<Directory /var/www/my_project/my_app/static>
    Require all granted
</Directory>

以上!!

参考にしたサイト様

AndroidのAlarmManagerに再度入門する

いつもAlarmMangerには悩まされるのので、再度調べてみたときの備忘録。。version追従辛い。。

AlarmManagerで設定するサンプル

Context context = ....;

// AlarmManagerを取得する
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

// Alarmのタイプ
int type = AlarmManager.RTC_WAKEUP;

// Alarmの開始日時
long triggerAtMills = Calendar.getInstance().getTimeInMillis();

// Alarmの繰り返し間隔
long intervalMills = AlarmManager.INTERVAL_DAY;

// Alarmの発火時に発行するIntent
int requestCode = 0;
Intent intent = new Intent(context, MyService.class);
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
PendingIntent operation = PendingIntent.getService(context, requestCode, intent, flags);

// AlarmManagerに登録する
alarmManager.setRepeating(type, triggerAtMillis, intervalMillis, operation);

// AlarmManagerから削除する
alarmManager.cancel(operation);

Alarmのタイプ

// 時刻
int type = AlarmManager.RTC;

// RTC + 実機スリープ中にはWAKE UP
int type = AlarmManager.RTC_WAKEUP;

// スリープ時間を含んだブートアップからの経過時間
int type = AlarmManager.ELAPSED_REALTIME;

// ELAPSED_REALTIME + 実機スリープ中にはWAKE UP
int type = AlarmManager.ELAPSED_REALTIME_WAKEUP;

Alarmの繰り返し間隔

// あらかじめ定数がいろいろ用意されているので、好きにつかう
long intervalMillis = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
long intervalMillis = AlarmManager.INTERVAL_HALF_HOUR;
long intervalMillis = AlarmManager.INTERVAL_HOUR;
long intervalMillis = AlarmManager.INTERVAL_HALF_DAY;
long intervalMillis = AlarmManager.INTERVAL_DAY;

PendingIntentのflags

//    PendingIntent存在しない場合, 新規作成せず、NULLを返す。
int flags = PendingIntent.FLAG_NO_CREATE;
//     PendingIntentが存在する場合, IntentのExtra Dataを更新する。
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
//     PendingIntentが存在している場合, 一度キャンセルしてから新規作成する。
int flags = PendingIntent.FLAG_CANCEL_CURRENT;
//     PendingIntentがただ一度のみ使われるときに利用する.
int flags = PendingIntent.FLAG_ONE_SHOT;

PendingIntentの作成方法

// Service
Intent intent = new Intent(context, MyService.class);
PendingIntent operation = PendingIntent.getService(context, requestCode, intent, flags);

// Broadcast Reciever
Intent intent = new Intent(context, MyReciever.class);
PendingIntent operation = PendingIntent.getBroadcast(context, requestCode, intent, flags);

// Activiy
Intent intent = new Intent(context, MyActivity.class);
PendingIntent operation = PendingIntent.getActivity(context, requestCode, intent, flags);

AlarmManagerへの登録

API 18(Android 4.3)

// *** ~ API 18(Android 4.3)
// 単発アラーム
alarmManager.set(type, triggerAtMillis, operation);
// 繰り返しアラーム
alarmManager.setRepeating(type, triggerAtMillis, intervalMillis, pendingIntent); 

API 19(Android 4.4) ~ API 22(Android 5.1)

// *** API 19(Android 4.4) ~ API 22(Android 5.1)
// 単発アラーム
alarmManager.set(type, triggerAtMillis, operation); // おおまかなアラーム
alarmManager.setExact(type, triggerAtMillis, operation); // 正確なアラーム

// 繰り返しアラーム
alarmManager.setRepeating(type, triggerAtMillis, intervalMillis, pendingIntent); // 正確なアラーム
alarmManager.setInexactRepeating(type, triggerAtMillis, intervalMillis, operation); // おおまかなアラーム
alarmManager.setWindow(type, triggerAtMillis, intervalMillis, operation); //設定したWindow内の正確なアラーム

API 23(Android 6.0) ~

// *** API 23(Android 6.0) ~
// 単発アラーム
alarmManager.set(type, triggerAtMillis, operation);
alarmManager.setAndAllowWhileIdle(type, triggerAtMillis, operation); // Doze中でも発火

alarmManager.setExact(type, triggerAtMillis, operation);
alarmManager.setExactAndAllowWhileIdle(type, triggerAtMillis, operation); // Doze中でも発火

// 繰り返しアラーム
alarmManager.setRepeating(type, triggerAtMillis, intervalMillis, pendingIntent);
alarmManager.setInexactRepeating(type, triggerAtMillis, intervalMillis, operation);
alarmManager.setWindow(type, triggerAtMillis, intervalMillis, operation);

AlarmManagerの登録情報を確認

$ adb shell dumpsys alarm

以上!!

参考にしたサイト様

Android7.xで電話・通話の自動応答できるようにするまで

Androidはバージョンが上がるごとにセキュリティ対策が進み、利用できなくなるAPIがある。
電話の自動応答もその一つで。数少ない情報も使えない場合が多い。

調べてやってみたこと

  1. ITelephonyを使って、acceptCall()を実行する(The internal ITelephony.aidl method)
  2. HeadSetの応答ボタンをエミュレート(The Headset KeyCode Intent Method)
  3. 内部のshellからinput keyeventを送信(Run input keyevent Method)

    • Android7.xから使えないらしい。Android6.x(Marshmallow)まではOK
  4. Telecom Frameworkを使う

Telecom Frameworkとは

公式の説明では、こんな感じ。

Android Telecom Frameworkは、Androidバイスでの音声通話とビデオ通話を管理します。 これには、ConnectionService APIの実装者が提供するVOIPコールだけでなく、 SIMベースのコール(テレフォニーフレームワークなど)も含まれます。

なるほど、通話の管理に対するフレームワークらしい。 更に読み進めると。

Telecomが扱う主な2つのコンポーネントは、ConnectionServicesとInCallServicesです。

ConnectionService実装は、何らかの手段(例えば、VOIP)を使用して別のパーティにコールを接続する役割を担います。 電話で最も一般的なConnectionServiceの実装は、キャリア呼び出しの接続を担当するTelephony ConnectionServiceです。

InCallService実装は、Telecomが管理する呼び出しにユーザーインターフェイスを提供し、ユーザーにこれらの呼び出しを制御したり操作したりする手段を提供します。 デバイスにバンドルされている電話アプリは、InCallServiceの実装の最も一般的な例です。

Telecomは、ConnectionService実装によって提供される呼び出しを、InCallService実装によって提供される呼び出し側ユーザーインターフェイスにルーティングする、スイッチボードとして機能します。

なにやら、2パターンあるらしい。

1, システムの電話アプリの代替品を作成したい 2. Androidの通話体験に統合された呼び出しソリューションを作成したい

うん。むずかしい。。。

1. Creating a Replacement Phone App(電話アプリを作りたい)

電話アプリを作りたいときは、InCallServiceを使えということらしい。

Android端末で標準のの電話アプリの代わりを作成する場合は、InCallService APIを実装します。

InCallServiceには呼び出し機能がなく、呼び出しのためのユーザーインターフェイスのみで構成されている必要があります。InCallServiceは、テレコムフレームワークが認識しているすべてのコールを処理する必要があります。

通話の性質(例えば、通話がSIMベースの電話通話であると仮定して)を想定してはいけません。また、ConnectionServiceに基づいて通話制限を実装してはいけません。

詳細については、「InCallService」を参照してください。

2. Integrating a Calling Solution(通話ソリューションをインテグレートしたい)

電話アプリではなく、通話をむにゃむにゃしたいときには、ConnectionServicesを使うらしい

呼び出し元のソリューションをAndroidに統合する場合は、次のオプションがあります。

2-1 Implement the self-managed ConnectionService API(全部自分で管理する)

これは、既定の電話アプリ内で自分の通話を表示したくないスタンドアロン通話アプリケーションの開発者にとって、またユーザーインターフェイスに他の通話を表示したくない場合に最適です。

従来、スタンドアロンの通話アプリケーションは、電話状態を聞いて他の通話がいつ行われているかを判断しようとしていました。

これは、電話機の状態が、ユーザがインストールした可能性のある他の通話アプリを考慮に入れていないため、問題である。

自己管理されたConnectionServiceを使用すると、デバイスでのネイティブテレフォニーコールだけでなく、このAPIを実装する他のスタンドアロンのコールアプリとの相互運用性を保証することができます。

セルフマネージドConnectionService APIは、オーディオルーティングとフォーカスも管理します。 詳細については、「自己管理接続サービス」を参照してください。

2-2 Implement the managed ConnectionService API(管理されたConnectionService APIを使う)

既存のデバイス電話アプリケーション(getDefaultDialerPackage()を参照)を使用して呼び出しのためのユーザーインターフェイスを提供する呼び出しソリューションの開発を容易にします。

例として、SIP通話の第三者実装、またはVOIP通話サービスがあります。

ConnectionServiceだけでは、コールの接続方法のみが提供されますが、関連付けられたユーザーインターフェイスはありません。

詳細については、ConnectionServiceを参照してください。

2-3 Implement both the InCallService and ConnectionService API(どっちも使う)

独自のConnectionServiceベースの呼び出しソリューションを独自のユーザーインターフェイスで作成し、同じユーザーインターフェイスで他のすべてのAndroid呼び出しを表示する場合に最適です。

この方法を使用する場合でも、InCallServiceが表示する呼び出しのソースについては想定しないでください。

カスタムのInCallServiceにデフォルトの電話アプリが設定されていなくても、ConnectionServiceの実装が機能していることを確認する必要があります。

InCallServiceを使ってみる

InCallService | Android Developersを見てみた。

このサービスは、電話を管理するためのユーザーインターフェイスを提供したいすべてのアプリによって実装されています。 Telecomは、ライブ(アクティブまたは着信)通話が存在している間にこのサービスにバインドし、通話中のアプリと最近切断された通話をインコールアプリに通知するために使用します。 テレコムサービスがInCallService実装にバインドされる前に、まずアプリケーションをデフォルトの電話アプリとして設定する必要があります(getDefaultDialerPackage()を参照)。

以下は、InCallServiceのマニフェスト登録の例です。 metadata(METADATA_IN_CALL_SERVICE_UI)は、この特定のInCallService実装が、組み込みのコールインUIを置き換えることを示しています。

UIは標準の電話アプリに任せて、それ以外の部分を操作できるよう。

参考にしたサイト様

昔の解決策の参考サイト様

公式サイトのReferencesたち

Telecom関連の参考サイト様

ConnectionService関連の参考サイト様

InCallService関連の参考サイト様

電話アプリ関連の参考サイト様

【小ネタ】取得したファイルをURIをRetrofitで送信する

表題まま。取得したファイルをURIをRetrofitで送信したときの備忘録。
そのままだとうまくいかないので、RetrofitをRequestBodyを継承して、
writeTo()時にファイル読み込むようにするといいっぽい

 private fun createRequestBody(uri: Uri, context: Context): RequestBody {
        val contentResolver = context.contentResolver
        val contentType = MediaType.parse(contentResolver.getType(uri))

        val projection = arrayOf(MediaStore.MediaColumns.SIZE)
        var fileName: String? = null
        val length = contentResolver
                .query(uri, projection, null, null, null)
                ?.use {
                    if (it.moveToFirst()) {
                        // URIから拡張子を取得
                        val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(context.contentResolver.getType(uri))
                        // ファイル名を取得
                        fileName = "${it.getString(0)}.$ext"
                        it.getLong(0)
                    } else null
                } ?: throw IllegalArgumentException("uriからファイルサイズを取得できません")

        return object : RequestBody() {

            override fun contentLength(): Long = length

            override fun contentType(): MediaType? = contentType

            override fun writeTo(sink: BufferedSink) {
                Okio.source(context.contentResolver.openInputStream(uri)).use {
                    sink.writeAll(it)
                }
            }

            override fun toString(): String {
                return fileName ?: super.toString()
            }
        }
    }

参考にしたサイト様

AndroidでRetrofit2をつかってみた

AndroidAPIクライアントライブラリのRetrofit2を使ってみたときの備忘録。

build.gradle
// Retrofit
compile 'com.squareup.retrofit2:retrofit:2.3.0'

// OkHttpのログを見たいとき
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'

// AdapterにRxJavaを使うとき
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' // RxJava2はこっち。RxJava1用もあるので注意
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'

// ConverterにGsonを使うとき
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.google.code.gson:gson:2.8.1'

How To Use

InterfaceでAPIを定義する

public interface ApiClient {
  @POST("login")
  Observable<LoginBean> login(@Query("user") String user, @Query("pass") String pass);
}

InterfaceからAPIクライアントを生成する

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);

 ApiClient apiClient = new Retrofit.Builder()
        .client(new OkHttpClient.Builder().addInterceptor(interceptor).build()) // ロガーを追加したOkHttpClientを設定
        .baseUrl("https://sample.com/")
        .addConverterFactory(GsonConverterFactory.create()) // ConverterにGsonを使う
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // CallAdapterにRxJava2を使う
        .build() // ビルダーを作って
        .create(ApiClient.class); // APIクライントを生成

How To Test

RxJava2のテストは非同期のため、通常通りではうまくいかない
テスト用のTestObserverがあるので、それを使ってテストする
※RxJava1ではTestSubscriberだったが、RxJava2ではTestObserverに置き換わった

@RunWith(AndroidJUnit4.class)
public class TestLoginUseCase {
    private static final String TAG = TestLoginUseCase.class.getSimpleName();

    @Test
    public void test_login_success() throws CtiException {
        // Dagger2を使っているので、そこからインスタンスを取得
        AppModule module = new AppModule(getApplication());
        ServerApi serverApi = module.provideServerApi();
        PrefHandler prefHandler = module.providePrefHandler(module.provideContext());
        prefHandler.setCCode("dev_test");
        LoginUseCase loginUseCase = module.provideLoginUseCase(serverApi, prefHandler);

        String uid = "admin001";
        String pwd = "123456";
        // テスト用のObserverを用意
        TestObserver<LoginBean> subscriber = TestObserver.create();
        loginUseCase.login(uid, pwd).subscribe(subscriber);

        subscriber.awaitTerminalEvent();  // CompleteかErrorになるまで待つ
        subscriber.assertComplete()         // Completeかどうかのアサート
                .assertNoErrors();                  // Errorがないかのアサート
        LoginBean loginBean = subscriber.values().get(0); // Completeになった結果を取得
        assertEquals("200", loginBean.getCode());
        assertEquals("success", loginBean.getStatus());
        assertEquals("success", loginBean.getMessage());
        assertEquals("success", loginBean.getExpiration_datetime());
    }

    AppApplication getApplication() {
        return (AppApplication) InstrumentationRegistry.getTargetContext().getApplicationContext();
    }
}

参考にしたサイト様