Google Awareness API 예제

|

Google Awareness API 사용 예제입니다. 일단 앞서 포스팅한 Google API Key 등록을 먼저 해야 합니다. Google API Key를 획득했다면 다음과 같이 AndroidManifest.xml 에 해당 키 정보를 기입해줍니다.


AndroidManifest.xml

<application
  android:allowBackup="true"
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:supportsRtl="true"
  android:theme="@style/AppTheme">

  <meta-data
    android:name="com.google.android.awareness.API_KEY"
    android:value="API_KEY" />

  <activity android:name=".MainActivity">
    <intent-filter>
      <action android:name="android.intent.action.MAIN" />

      <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
  </activity>
</application>


Activity_main.xml

그리고 간단한 버튼 4개를 배치해서 테스트를 하기 위한 activity_main.xml 코드는 다음과 같습니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/activity_main"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:paddingBottom="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:orientation="vertical">

  <Button
    android:id="@+id/btn_get_location"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Get Location" />

  <Button
    android:id="@+id/btn_get_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Get Activity" />

  <Button
    android:id="@+id/btn_get_place"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Get Place" />

  <Button
    android:id="@+id/btn_get_weather"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Get Weather" />

  <ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
      android:id="@+id/textview"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />

  </ScrollView>
</LinearLayout>


MainActivity.java

마지막으로, MainActivity.java 코드입니다.

package com.datacafe.googleawarenessapi;

import android.content.pm.PackageManager;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.google.android.gms.awareness.Awareness;
import com.google.android.gms.awareness.snapshot.DetectedActivityResult;
import com.google.android.gms.awareness.snapshot.LocationResult;
import com.google.android.gms.awareness.snapshot.PlacesResult;
import com.google.android.gms.awareness.snapshot.WeatherResult;
import com.google.android.gms.awareness.state.Weather;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;
import com.google.android.gms.location.places.PlaceLikelihood;

import java.util.List;

public class MainActivity extends AppCompatActivity {

  //Activities
  //public static final int IN_VEHICLE = 0;
  //public static final int ON_BICYCLE = 1;
  //public static final int ON_FOOT = 2;
  //public static final int STILL = 3;
  //public static final int UNKNOWN = 4;
  //public static final int TILTING = 5;
  //public static final int WALKING = 7;
  //public static final int RUNNING = 8;

  private GoogleApiClient mGoogleApiClient;
  private TextView mDebugLogView;

  private StringBuilder mLogBuilder = new StringBuilder();

  private void log(String message) {
    mLogBuilder.append(message);
    mLogBuilder.append("\n");

    if(mDebugLogView != null) {
      mDebugLogView.setText(mLogBuilder.toString());
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mDebugLogView = (TextView) findViewById(R.id.textview);

    findViewById(R.id.btn_get_location).setOnClickListener(mOnClickListener);
    findViewById(R.id.btn_get_activity).setOnClickListener(mOnClickListener);
    findViewById(R.id.btn_get_place).setOnClickListener(mOnClickListener);
    findViewById(R.id.btn_get_weather).setOnClickListener(mOnClickListener);

    Log.i("snowdeer", "[snowdeer] start...");

    mGoogleApiClient = new GoogleApiClient.Builder(this)
        .addApi(Awareness.API)
        .build();
    mGoogleApiClient.connect();

    Log.i("snowdeer", "[snowdeer] GoogleApi connect..");


  }

  private View.OnClickListener mOnClickListener = new View.OnClickListener() {

    @Override
    public void onClick(View view) {
      switch(view.getId()) {
        case R.id.btn_get_location: {
          if(ContextCompat.checkSelfPermission(
              MainActivity.this,
              android.Manifest.permission.ACCESS_FINE_LOCATION) !=
              PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                MainActivity.this,
                new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                100
            );
            return;
          }

          Awareness.SnapshotApi.getLocation(mGoogleApiClient)
              .setResultCallback(new ResultCallback<LocationResult>() {
                @Override
                public void onResult(@NonNull LocationResult locationResult) {
                  if(!locationResult.getStatus().isSuccess()) {
                    log("Could not get location.");
                    return;
                  }
                  Location location = locationResult.getLocation();
                  log("Lat: " + location.getLatitude()
                      + ", Lon: " + location.getLongitude());
                }
              });
          break;
        }
        case R.id.btn_get_activity:
          Awareness.SnapshotApi.getDetectedActivity(mGoogleApiClient)
              .setResultCallback(new ResultCallback<DetectedActivityResult>() {
                @Override
                public void onResult(@NonNull DetectedActivityResult detectedActivityResult) {
                  if(!detectedActivityResult.getStatus().isSuccess()) {
                    log("Could not get the current activity.");
                    return;
                  }
                  ActivityRecognitionResult ar = detectedActivityResult
                      .getActivityRecognitionResult();
                  DetectedActivity probableActivity = ar.getMostProbableActivity();
                  log(probableActivity.toString());
                }
              });
          break;

        case R.id.btn_get_place: {
          if(ContextCompat.checkSelfPermission(
              MainActivity.this,
              android.Manifest.permission.ACCESS_FINE_LOCATION) !=
              PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                MainActivity.this,
                new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                100
            );
            return;
          }

          Awareness.SnapshotApi.getPlaces(mGoogleApiClient)
              .setResultCallback(new ResultCallback<PlacesResult>() {
                @Override
                public void onResult(@NonNull PlacesResult placesResult) {
                  if(!placesResult.getStatus().isSuccess()) {
                    log("Could not get places.");
                    return;
                  }
                  List<PlaceLikelihood> placeLikelihoodList = placesResult.getPlaceLikelihoods();

                  if(placeLikelihoodList == null) {
                    log("Result List is Null!!");
                    return;
                  }
                  // Show the top 5 possible location results.
                  for(int i = 0; i < placeLikelihoodList.size(); i++) {
                    PlaceLikelihood p = placeLikelihoodList.get(i);
                    log(p.getPlace().getName().toString()
                        + ", likelihood: " + p.getLikelihood());
                  }
                }
              });
          break;
        }

        case R.id.btn_get_weather: {
          if(ContextCompat.checkSelfPermission(
              MainActivity.this,
              android.Manifest.permission.ACCESS_FINE_LOCATION) !=
              PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                MainActivity.this,
                new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                100
            );
            return;
          }

          Awareness.SnapshotApi.getWeather(mGoogleApiClient)
              .setResultCallback(new ResultCallback<WeatherResult>() {
                @Override
                public void onResult(@NonNull WeatherResult weatherResult) {
                  if(!weatherResult.getStatus().isSuccess()) {
                    log("Could not get weather.");
                    return;
                  }
                  Weather weather = weatherResult.getWeather();
                  log("Weather: " + weather);
                }
              });

          break;
        }
      }
    }
  };
}

Google API Key 등록하기

|

Google Awareness API를 이용한 간단한 프로그램을 개발할 일이 생겼습니다. 하지만, Awareness API를 쓰기 위해서는 Google로부터 Google API Key를 등록 및 승인받아야만 사용이 가능했습니다. 그래서 여기서는 Google API Key를 등록하는 방법을 포스팅해보록 하겠습니다.


Developer Console 접속

일단 구글 개발자 콘솔에 접속한다.

image -fullwidth

여기에서 왼쪽 편의 메뉴에서 라이브러리를 선택합니다.

image -fullwidth

그리고 원하는 API를 선택합니다. 저는 Awareness API를 사용할 것이기 때문에 Awareness API를 선택했습니다. API 종류가 많기 때문에 검색을 이용하면 좀 더 편리하게 API를 찾을 수 있습니다.

image -fullwidth

화면 가운데 위쪽에 있는 사용 설정 글씨를 클릭해줍니다. 버튼 형태가 아니라서 찾기가 어려웠네요.

image -fullwidth

화면 오른편 위쪽에 사용자 인증 정보 만들기 버튼을 클릭합니다.

image -fullwidth

어떤 사용자 정보가 필요한가요 버튼을 눌러줍니다.

image -fullwidth

드디어 API 키가 생성되었다. 여기에서 키 제한하기를 눌러서 키 사용의 제한을 걸어두도록 합니다.

image -fullwidth

저는 Android 용으로 사용하기 위해서 Android 앱을 선택했습니다.

Android 앱을 선택하면 Android App의 패키지 이름과 Signed Key의 SHA1 값을 요구합니다. Signed Key의 SHA1은 화면에 있는 명령어를 커맨드 라인에서 입력하여 얻을 수 있습니다. mystore.keystore 대신 자신의 key 이름을 입력하면 됩니다.

signingConfigs{
keytool -list -v -keystore mystore.keystore

그러면 다음과 같은 출력 결과가 나오고, 이 중에서 SHA1 값만 있으면 됩니다.

image -fullwidth

App의 패키지 이름과 Signed Key의 SHA1 값을 획득하면, 위의 화면에서 패키지 이름 및 지문 추가 버튼을 눌러서 각 값을 입력해주면 됩니다.

Android Studio Gradle 에서 빌드시 Signed Key 설정하기

|

Android Studio에서 빌드할 때 Signed Key를 설정하는 방법을 포스팅하도록 하겠습니다.

물론, 본문의 내용과 곤계 없이 Android Studio에서 빌드를 할 때, Release로 메뉴에서 Generate Signed APK를 선택하여 빌드하는 방법도 있습니다. 하지만, 매번 빌드할 때마다 일일이 메뉴로 들어가서 해당 기능으로 APK를 생성하는 건 여간 번거로운 일입니다. 게다가 Release 모드가 아닌 평소 개발용인 Debug 모드로 빌드할 때도 Signed Key를 설정해야 하는 경우도 발생할 수 있기 때문에 여기서는 gradle 설정을 통해 Signed Key를 설정하는 방법을 포스팅해보겠습니다.


build.gradle 설정

app의 build.gradle 파일을 엽니다. 그리고 아래와 같이

signingConfigs{
        releaseWithSignedKey {
            storeFile file("SampleKeyStore.jks")
            storePassword "password1"
            keyAlias "sample"
            keyPassword "password2"
        }
    }

항목을 만들어 줍니다. 그리고, buildType에 아래와 같이

buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.releaseWithSignedKey
        }
    }

와 같이 설정해주면 됩니다. 그리고 만약 Debug 모드에서도 Signed Key를 참조하게 하려면 다음과 같이 하면 됩니다.

buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.releaseWithSignedKey
        }
        debug{
            signingConfig signingConfigs.releaseWithSignedKey
        }
    }


그런데, 이렇게 하면 build.gradle에 KeyStore의 민감한 정보들이 너무 노출이 되고 관리도 번거로워지기 때문에 각 값들을 변수 처리해주는 것이 깔끔합니다. 이 때는 Project의 gradle.properties에

SIGNED_STORE_FILE=SampleKeyStore.jks
SIGNED_STORE_PASSWORD=password1
SIGNED_KEY_ALIAS=sample
SIGNED_KEY_PASSWORD=password2

와 같이 작성하고, build.gradle에는

signingConfigs{
        releaseWithSignedKey {
            storeFile file(SIGNED_STORE_FILE)
            storePassword SIGNED_STORE_PASSWORD
            keyAlias SIGNED_KEY_ALIAS
            keyPassword SIGNED_KEY_PASSWORD
        }
    }

와 같이 작성을 해줍니다.


여기까지 적용한 후 Run을 해보면, Signed Key가 정상적으로 적용된 것을 알 수 있습니다.


전체 코드

참고로, 전체 build.gradle 파일 내용을 첨부하도록 하겠습니다.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.0"
    defaultConfig {
        applicationId "com.datacafe.googleawarenessapi"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    signingConfigs{
        releaseWithSignedKey {
            storeFile file(SIGNED_STORE_FILE)
            storePassword SIGNED_STORE_PASSWORD
            keyAlias SIGNED_KEY_ALIAS
            keyPassword SIGNED_KEY_PASSWORD
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.releaseWithSignedKey
        }
        debug{
            signingConfig signingConfigs.releaseWithSignedKey
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.0.1'


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

    testCompile 'junit:junit:4.12'
}

CardView 스타일 Drawable

|

안드로이드 L(롤리팝) 부터 CardView라는 UX 컴포넌트가 추가되었습니다. 하지만 여기서는 그 이전 버전에서도 사용할 수 있도록 CardView와 유사한 형태의 Drawable를 만들어보고자 합니다.

먼저 drawable 폴더에 각 xml 파일들을 작성합니다.


layer_card_background.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
    <shape android:shape="rectangle">
      <solid android:color="#CABBBBBB" />
      <corners android:radius="2dp" />
    </shape>
  </item>

  <item
    android:bottom="2dp"
    android:left="0dp"
    android:right="0dp"
    android:top="0dp">
    <shape android:shape="rectangle">
      <solid android:color="@android:color/white" />
      <corners android:radius="2dp" />
    </shape>
  </item>
</layer-list>


layer_card_background_selected.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
    <shape android:shape="rectangle">
      <solid android:color="#CABBBBBB" />
      <corners android:radius="2dp" />
    </shape>
  </item>

  <item
    android:bottom="2dp"
    android:left="0dp"
    android:right="0dp"
    android:top="0dp">
    <shape android:shape="rectangle">
      <solid android:color="#CCCCCC" />
      <corners android:radius="2dp" />
    </shape>
  </item>
</layer-list>


selector_card_background.xml

<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item
    android:drawable="@drawable/layer_card_background_selected"
    android:state_pressed="true" />

  <item android:drawable="@drawable/layer_card_background" />
</selector>


ListView

<ListView
  android:cacheColorHint="@android:color/transparent"
  android:divider="@null"
  android:dividerHeight="10dp"
  android:footerDividersEnabled="true"
  android:headerDividersEnabled="true"
  android:id="@+id/listview"
  android:layout_above="@id/layout_bottom_buttons"
  android:layout_height="match_parent"
  android:layout_width="match_parent"
  android:listSelector="@android:color/transparent" />


각 Item들을 출력하는 View

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/layout_background"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingLeft="15dp"
  android:paddingRight="15dp"
  android:descendantFocusability="beforeDescendants">

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="15dp"
    android:paddingBottom="15dp"
    android:paddingLeft="15dp"
    android:paddingRight="15dp"
    android:background="@drawable/selector_card_background"
    android:descendantFocusability="afterDescendants"
    android:orientation="horizontal">

    <ImageView
      android:layout_width="64dp"
      android:layout_height="64dp"
      android:layout_gravity="center_vertical"
      android:src="@drawable/footprint" />

    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginLeft="15dp"
      android:orientation="vertical">

      <TextView
        android:id="@+id/tv_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="2016-11-02 13:30:30"
        android:textColor="@color/darkgray"
        android:textSize="14sp" />

      <TextView
        android:id="@+id/tv_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Beacon1 : 35"
        android:textColor="@color/black"
        android:textSize="12sp" />
    </LinearLayout>


  </LinearLayout>

</FrameLayout>

ViewPager 사용 예제

|

안드로이드 ViewPager를 활용하는 간단한 예제 코드입니다.


Layout 코드

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.PagerTitleStrip
      android:id="@+id/pager_title_strip"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:paddingBottom="8dp"
      android:paddingTop="8dp"/>

  </android.support.v4.view.ViewPager>

</RelativeLayout>


그리고 Java 코드는 다음과 같습니다. 이번 예제 코드는 Fragment에서 ViewPager를 사용할 경우의 코드인데, Activity에서 사용할 경우에도 큰 차이는 없습니다.

Java 코드

package com.lnc.prototype.ui.main;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.lnc.prototype.R;

public class LocationDataFragment extends Fragment {

  private ViewPager mViewPager;
  private ViewPagerAdapter mViewPagerAdapter;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_location_data,
        container, false);

    mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
    mViewPagerAdapter = new ViewPagerAdapter(getFragmentManager());
    mViewPager.setAdapter(mViewPagerAdapter);

    return view;
  }


  private class ViewPagerAdapter extends FragmentStatePagerAdapter {

    public ViewPagerAdapter(FragmentManager fm) {
      super(fm);
    }

    @Override
    public int getCount() {
      return 2;
    }

    @Override
    public Fragment getItem(int position) {
      Fragment fragment = null;
      switch(position) {
        case 0:
          fragment = new BeaconRadarFragment();
          break;

        case 1:
          fragment = new LocationHistoryFragment();
          break;
      }
      return fragment;
    }

    @Override
    public CharSequence getPageTitle(int position) {
      switch(position) {
        case 0:
          return "Beacon Radar";
        case 1:
          return "Location History";
      }

      return super.getPageTitle(position);
    }
  }

  ;
}