Smart Mirror 스마트 미러 만들기- 7 소프트웨어 - 앱 Application 편
Github
https://github.com/KimJungChul/smartmirror-web-application
Youtube
https://www.youtube.com/watch?v=1jEWvWtXniQ
오랜만에 스마트 미러편으로 포스팅합니다.
앱에 대해서 많은 분들이 궁금해하셨고, 늦게 포스팅하여 죄송합니다.
APP(Application)의 주요 기능
앱의 주요 기능으로는 딱 두 가지입니다.
(스마트 미러가 와이파이로 인터넷을 접속한다면, 앱도 동일한 와이파이에 접속하셔야 하고, 또한 앱의 IP 설정도 미러의 IP로 변경하셔야 통신이 가능합니다. )
1. 음성 인식 STT 전송
스마트 미러의 음성 인식 API(Speech Recongizer, Annyang API)가 오후 4시 경에 되는 제한이 있고자 하여,
그 문제점을 해결하고자 스마트 폰의 앱에서 미러 대신 음성인식(Google Speech API, STT(Speech To Text) 여기서는 시간 리밋 제한이 없습니다.)을 받아 스마트 미러의 웹 서버로 명령어를 전송하는 것입니다. 웹 서버로 전송하기 위해서 미러 쪽에서는 Express를 이용한 웹 서버에서 GET으로 통신을 받아야 하며, 앱 단에서는 STT를 통해 인식 받은 Text를 비동기식 AsyncTask의 HttpURLConnection으로 연결해 값을 전송해야 합니다. 이후에 다시 언급하도록 하겠습니다.
2. 스마트 폰 Notification 전송(전화,문자,메일,카카오톡,페이스북 알림)
Android에서는 NotificationListenerService를 통해, 각종 알림의 패키지 정보와 제목과 내용을 알아낼 수 있습니다. 이를 테면 카톡에서는 보낸 사용자는 누구이며, 내용은 어떠하고, 또한 사용자의 프로필 이미지도 알아 낼 수 있죠. 그러한 정보를 스마트 미러로 보내어, 알림 받는 기능을 구현한 것입니다. 이 또한 미러에서는 GET으로 데이터를 받으며, 앱에서는 HttpURLConnection으로 데이터를 전송합니다.
APP UI
인트로 화면 Splash | Facebook 로그인 화면 |
유저 정보 화면 | 앱의 IP설정화면 |
음성 인식 화면 부분(STT) | 개발자 정보 화면 |
APP Source
actionbarsherlock
slidingmenu_library
위의 import된 두 개의 패키지는 슬라이딩 메뉴를 위한 것입니다.
facebook api의 LoginButton 및 페이스북 프로필 정보를 가져오기 위해 사용하였습니다.
app
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="softwarek.kimjungchul.smartmirror" >
/* 인터넷 사용 퍼미션*/
<uses-permission android:name="android.permission.INTERNET" />
/* 핸드폰 정보 읽어오는 퍼미션*/
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
/* 사용자 정보 읽어오는 퍼미션*/
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
/* 음성 인식을 위한 permission*/
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application
android:allowBackup="true"
android:icon="@drawable/smartmirroricon"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
/* 페이스북 API 사용*/
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" /> // 페이스북 앱 개발 승인 받은 KEY를 넣어주세요
<activity
android:name=".SplashActivity"
android:label="@string/app_name"
android:theme="@style/Theme.Sherlock.NoActionBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".LoginActivity"
android:label="@string/app_name"
android:theme="@style/Theme.Sherlock.NoActionBar" />
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
</activity>
/* Notification*/
<service
android:name=".NotiListenService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
</manifest>
SplashActivity.java
SharedPreference 환경 변수를 사용해 페이스북 로그인을 했는지 아닌지를 분별합니다.
로그인을 안했다면 LoginActivity, 로그인을 했다면 MainActivity로 이동합니다.
SPLASH_DISPLAY_LENGTH의 2000은 2초를 의미하며, 시간을 조정하실 수 있습니다.
package softwarek.kimjungchul.smartmirror;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
public class SplashActivity extends Activity {
/** Duration of wait **/
private final int SPLASH_DISPLAY_LENGTH = 2000;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_splash);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
/** SharedPreference 환경 변수 사용 **/
SharedPreferences prefs = getSharedPreferences("login",0);
/** prefs.getString() return값이 null이라면 2번째 함수를 대입한다. **/
String facebook_login = prefs.getString("FACEBOOK_LOGIN","LOGOUT");
Log.d("FACEBOOK_LOGIN::::::", facebook_login);
/** 환경 변수 중 로그인 부분을 체킹하여 Activity 전환 */
// 페이스북 로그인
if(facebook_login.equals("LOGIN")) {
Intent mainIntent = new Intent(SplashActivity.this, MainActivity.class);
SplashActivity.this.startActivity(mainIntent);
SplashActivity.this.finish();
}
// 페이스북 로그아웃
else if(facebook_login.equals("LOGOUT")) {
Intent loginIntent = new Intent(SplashActivity.this, LoginActivity.class);
SplashActivity.this.startActivity(loginIntent);
SplashActivity.this.finish();
}
}
}, SPLASH_DISPLAY_LENGTH);
}
}
LoginActivity.java
페이스북 로그인 버튼을 만들어, 페이스북 로그인을 구현하였습니다.
자세한 사항은 페이스북 개발 Doc을 참고하시면 이해가 가실 것입니다.
사용자 디바이스 핸드폰 정보를 가져와 핸드폰 번호를 알 수 있으며,
사용자 핸드폰의 동기화된 구글 메일 주소도 가져올 수 있습니다.
자체 가입 기능을 만드신다면 참고해주세요 :D
package softwarek.kimjungchul.smartmirror;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Patterns;
import android.widget.Toast;
import com.facebook.AccessToken;
import com.facebook.CallbackManager;
import com.facebook.FacebookCallback;
import com.facebook.FacebookException;
import com.facebook.FacebookSdk;
import com.facebook.Profile;
import com.facebook.login.LoginResult;
import com.facebook.login.widget.LoginButton;
import java.util.regex.Pattern;
public class LoginActivity extends Activity {
private CallbackManager mCallbackManger;
/* facebook profile */
Profile profile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FacebookSdk.sdkInitialize(getApplicationContext());
setContentView(R.layout.activity_login);
mCallbackManger = CallbackManager.Factory.create();
LoginButton loginButton = (LoginButton)findViewById(R.id.login_button);
loginButton.registerCallback(mCallbackManger, new FacebookCallback<LoginResult>() {
@Override
public void onSuccess(LoginResult loginResult) {
AccessToken.setCurrentAccessToken(loginResult.getAccessToken());
/* 페이스북 프로필을 가져온다*/
profile = Profile.getCurrentProfile();
String userId = profile.getId();
String user_name = profile.getName();
String user_password = "";
/* 사용 디바이스 핸드폰 번호 알아오기*/
TelephonyManager systemService = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String user_phone = systemService.getLine1Number();
user_phone = user_phone.substring(user_phone.length() - 10, user_phone.length());
user_phone = "0" + user_phone;
/* 안드로이드 기기 구글 동기화 이메일 주소 가져오기 */
Pattern emailPattern = Patterns.EMAIL_ADDRESS;
AccountManager am = (AccountManager)getSystemService(Context.ACCOUNT_SERVICE);
Account[] accounts = am.getAccounts();
String user_email="";
for (Account account : accounts) {
if (emailPattern.matcher(account.name).matches()) {
user_email = account.name;
}
}
/** SharedPreferences로 Facbook 정보 저장*/
/* 로그인될 시 세션을 유지하기 위해 환경 변수에 facebook login session 관리를 저장한다. */
SharedPreferences prefs = getSharedPreferences("login", 0);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("FACEBOOK_LOGIN", "LOGIN");
editor.commit();
/* 페이스 북 로그인이 처음이라면 SharedPreferences와 DB에 저장해야 한다.*/
String facebookFirstLogin = prefs.getString("FACEBOOK_FIRST_LOGIN","TRUE");
Log.d("Facebook ::::", "First Login ? " + facebookFirstLogin);
if(facebookFirstLogin.equals("TRUE")) {
/** SharedPreferences 저장*/
editor.putString("FACEBOOK_FIRST_LOGIN","FALSE");
editor.putString("USER_ID", userId);
editor.putString("USER_NAME", user_name);
editor.putString("USER_PASSWORD", user_password);
editor.putString("USER_PHONE", user_phone);
editor.putString("USER_EMAIL", user_email);
editor.commit();
Log.d("FaceBook :::", "id : " + userId);
Log.d("FaceBook :::", "name : " + user_name);
Log.d("FaceBook :::", "phone : " + user_phone);
Log.d("FaceBook :::", "email : " + user_email);
}
Intent mainIntent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(mainIntent);
finish();
}
@Override
public void onCancel() {
Toast.makeText(LoginActivity.this, "페이스북 로그인이 취소 되었습니다.", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(FacebookException error) {
Toast.makeText(LoginActivity.this, "페이스북 로그인이 취소 되었습니다.", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mCallbackManger.onActivityResult(requestCode, resultCode, data);
}
}
NotiListenService.java
핵심 기능 중에 한 부분으로서, 스마트 미러로 Notification 알림을 전송하는 부분입니다.
사용자가 앱에 설정해놓은 ip주소를 통해 데이터를 보내며,
여기서의 데이터는 알람으로 온 앱에 대한 패키지 이름과, 제목, 내용입니다. 문자열이며
앱 단에서 메일, 전화, 문자, 카카오톡, 페이스북 패키지 이름과 비교하여, 해당하는 것만 전송합니다.
HttpURLConnection을 이용해 스마트 미러로 GET통신을 이용해 전송합니다.
package softwarek.kimjungchul.smartmirror;
import android.app.Notification;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
public class NotiListenService extends NotificationListenerService {
private String smartmirror_ip; // 스마트 미러의 할당받은 ip 주소
/* 환경 변수 */
private SharedPreferences prefs;
public NotiListenService() {
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
//super.onNotificationPosted(sbn);
/**
* 보안 / 알림 액세스 / 알림 설정해야함!
*/
Notification noti = sbn.getNotification();
Bundle bundle = noti.extras;
prefs = getSharedPreferences("login", 0);
smartmirror_ip = prefs.getString("SMARTMIRROR_IP", "0.0.0.0");
Log.d("smartmirror IP : ",smartmirror_ip);
/*
int icon = bundle.getInt(Notification.EXTRA_SMALL_ICON);
Bitmap notificationLargeIcon = ((Bitmap) bundle.getParcelable(Notification.EXTRA_LARGE_ICON));
*/
/**
* strPacakge : 패캐지 이름
* title : 이름, 사용자, 전화번호 등...
* text : 내용 */
String strPackage = sbn.getPackageName();
String title = bundle.getString(Notification.EXTRA_TITLE);
CharSequence text = bundle.getCharSequence(Notification.EXTRA_TEXT);
if(title == null) title = "";
if(text == null) text = "";
Log.d("[noti] get :","package : "+strPackage+" // title : "+title+" // text : "+text);
/* 페이스북, 구글 메일, 카카오톡, 전화, 문자 패키지만 라즈베리파이 알림으로 전송 */
if(strPackage.equals("com.facebook.orca") || strPackage.equals("com.facebook.katana") || strPackage.equals("com.android.mms")
|| strPackage.equals("com.kakao.talk") || strPackage.equals("com.google.android.gm") || strPackage.equals("com.lge.ltecall")) {
/** 라즈베리 파이2 전송하는 부분 */
try {
String enPackage = URLEncoder.encode(strPackage, "UTF-8");
String enTitle = URLEncoder.encode(title, "UTF-8");
String enText = URLEncoder.encode(text.toString(), "UTF-8");
/* 라즈베리 파이 IP : 9090 (port) */
String urlRasp = "http://"+smartmirror_ip+":9090/noti.do?package=" + enPackage + "&title=" + enTitle + "&text=" + enText;
Log.d("smartmirror url : ",urlRasp);
URL url = new URL(urlRasp);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String response = null;
while (true) {
response = br.readLine();
if (response == null) break;
Log.d("[noti] response :", response);
}
urlConnection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
//super.onNotificationRemoved(sbn);
}
}
MainActivity.java
메인 액티비티에서는 전반적인 슬라이딩 메뉴에 대한 구성을 만들어
각각의 메뉴에 대한 Fragment를 호출하는 부분입니다.
package softwarek.kimjungchul.smartmirror;
import android.app.ActionBar;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.MenuItem;
import com.facebook.FacebookSdk;
import java.util.ArrayList;
import softwarek.kimjungchul.smartmirror.Adapter.NavDrawerItem;
import softwarek.kimjungchul.smartmirror.Adapter.NavDrawerListAdapter;
public class MainActivity extends SherlockFragmentActivity {
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
private ActionBarDrawerToggle mDrawerToggle;
// nav drawer title
private CharSequence mDrawerTitle;
// used to store app title
private CharSequence mTitle;
// slide menu items
private String[] navMenuTitles;
private TypedArray navMenuIcons;
private ArrayList<NavDrawerItem> navDrawerItems;
private NavDrawerListAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FacebookSdk.sdkInitialize(getApplicationContext());
mTitle = mDrawerTitle = getTitle();
// load slide menu items
navMenuTitles = getResources().getStringArray(R.array.nav_drawer_items);
// nav drawer icons from resources
navMenuIcons = getResources()
.obtainTypedArray(R.array.nav_drawer_icons);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.list_slidermenu);
navDrawerItems = new ArrayList<NavDrawerItem>();
// adding nav drawer items to array
/* 개인 유저 정보*/
navDrawerItems.add(new NavDrawerItem(navMenuTitles[0],navMenuIcons.getResourceId(0, -1) ));
/* 미러 음성 전송 */
navDrawerItems.add(new NavDrawerItem(navMenuTitles[1], navMenuIcons.getResourceId(1, -1)));
/* 미러 IP 설정 */
navDrawerItems.add(new NavDrawerItem(navMenuTitles[2], navMenuIcons.getResourceId(2, -1)));
/* 스마트 미러 정보 */
navDrawerItems.add(new NavDrawerItem(navMenuTitles[3], navMenuIcons.getResourceId(3, -1)));
// Recycle the typed array
navMenuIcons.recycle();
mDrawerList.setOnItemClickListener(new SlideMenuClickListener());
// setting the nav drawer list adapter
adapter = new NavDrawerListAdapter(getApplicationContext(),
navDrawerItems);
mDrawerList.setAdapter(adapter);
// enabling action bar app icon and behaving it as toggle button
ActionBar actionBar = getActionBar();
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
R.drawable.apptheme_ic_navigation_drawer, //nav menu toggle icon
R.string.app_name, // nav drawer open
R.string.app_name // nav drawer close
) {
public void onDrawerClosed(View view) {
getActionBar().setTitle(mTitle);
// calling onPrepareOptionsMenu() to show action bar icons
invalidateOptionsMenu();
}
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(mDrawerTitle);
// calling onPrepareOptionsMenu() to hide action bar icons
invalidateOptionsMenu();
}
};
mDrawerLayout.setDrawerListener(mDrawerToggle);
if (savedInstanceState == null) {
// on first time display view for first nav item
displayView(1);
}
}
/**
* 슬라이드 메뉴 클릭 리스너
*/
private class SlideMenuClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// display view for selected nav drawer item
displayView(position);
}
}
/** 슬라이딩 메뉴 오픈 / 클로즈 */
@Override
public boolean onOptionsItemSelected(MenuItem item) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
switch (item.getItemId()) {
case android.R.id.home:
if (mDrawerLayout.isDrawerOpen(mDrawerList)) {
mDrawerLayout.closeDrawer(mDrawerList);
} else {
mDrawerLayout.openDrawer(mDrawerList);
}
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
/**
* Diplaying fragment view for selected nav drawer list item
* 슬라이딩 메뉴를 프래그먼트와 연동
* */
private void displayView(int position) {
// update the main content by replacing fragments
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
switch (position) {
case 0:
ft.replace(R.id.frame_container,new HomeFragment());
break;
case 1:
ft.replace(R.id.frame_container,new SetIPFragment());
break;
case 2:
ft.replace(R.id.frame_container,new SendSpeechCommandFragment());
break;
case 3:
ft.replace(R.id.frame_container,new SmartMirrorFragment());
break;
default:
break;
}
ft.commit();
mDrawerList.setItemChecked(position, true);
mDrawerLayout.closeDrawer(mDrawerList);
}
@Override
public void setTitle(CharSequence title) {
mTitle = title;
getActionBar().setTitle(mTitle);
}
/**
* When using the ActionBarDrawerToggle, you must call it during
* onPostCreate() and onConfigurationChanged()...
*/
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggls
mDrawerToggle.onConfigurationChanged(newConfig);
}
}
HomeFragment.java
페이스북 로그인을 한 유저의 정보를 살펴볼 수 있습니다.
또한 로그아웃이 가능하도록 버튼을 구성하였습니다.
package softwarek.kimjungchul.smartmirror;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.facebook.FacebookSdk;
import com.facebook.Profile;
import com.facebook.login.LoginManager;
import com.facebook.login.widget.ProfilePictureView;
public class HomeFragment extends Fragment
{
private Profile profile;
private TextView tv_home_user_id;
private TextView tv_home_user_name;
private ProfilePictureView profilePictureView;
private ProgressDialog mProgressDialog;
@Override
public void onCreate(Bundle instance)
{
super.onCreate(instance);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
FacebookSdk.sdkInitialize(view.getContext());
profile = Profile.getCurrentProfile();
mProgressDialog = new ProgressDialog(getActivity());
mProgressDialog.setMessage("Log out ...");
String id = profile.getId();
String name = profile.getName();
tv_home_user_id = (TextView)view.findViewById(R.id.home_user_id);
tv_home_user_name = (TextView)view.findViewById(R.id.home_user_name);
tv_home_user_id.setText(id);
tv_home_user_name.setText(name);
profilePictureView = (ProfilePictureView) view.findViewById(R.id.home_profile_image_facebook);
profilePictureView.setProfileId(id);
/** Facebook logout*/
Button logoutButton = (Button) view.findViewById(R.id.logout_button);
logoutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Handler handler = new Handler();
mProgressDialog.setCancelable(false);
mProgressDialog.show();
handler.postDelayed(new Runnable() {
@Override
public void run() {
/* -- switch -> Button R.id. */
LoginManager.getInstance().logOut();
SharedPreferences prefs = getActivity().getSharedPreferences("login", 0);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("FACEBOOK_LOGIN", "LOGOUT");
editor.commit();
Intent mainIntent = new Intent(getActivity(), SplashActivity.class);
startActivity(mainIntent);
getActivity().finish();
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
}
}, 1000);
}
});
return view;
}
}
SendSpeechCommandFragment
이어서 핵심 기능 중 STT를 이용해 음성인식을 통해 사용자의 음성을 텍스트로 변환해 스마트 미러로 전송하는 부분입니다. 정말 힘들게 자료 수집하고 구현한 부분입니다.
음성인식 버튼을 누르면, 무한 Loop 돌 듯이 계속 사용자의 음성 인식을 시작합니다.
음성으로 ‘정지’ 또는 음성 명령 정지 버튼을 통해 음성 인식은 정지됩니다.
음성 인식 받은 데이터는 HttpURLConnection으로 전송하며, Notification 전송과 동일합니다.
package softwarek.kimjungchul.smartmirror;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockFragment;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
public class SendSpeechCommandFragment extends SherlockFragment{
private Intent i;
private TextView tv_speech;
private ImageButton btn_userSpeech;
private Button btn_stopRecog;
private SpeechRecognizer mRecognizer;
private String input_sst; // 명령 결과
private String enCommand;
private String smartmirror_ip; // 스마트 미러의 할당받은 ip 주소
/* 환경 변수 */
private SharedPreferences prefs;
/* 스마트 미러로 네트워크 전송 부분*/
SendComandTask sendComandTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_sendspeechcommend, container, false);
/* 레이아웃의 컴포넌트를 가져옵니다.*/
tv_speech = (TextView) rootView.findViewById(R.id.tv_speech);
btn_userSpeech = (ImageButton) rootView.findViewById(R.id.userSpeech);
btn_stopRecog = (Button) rootView.findViewById(R.id.btn_stopRecog);
i = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); // Intent 생성
i.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,getActivity().getPackageName()); // 호출한 패키지
i.putExtra(RecognizerIntent.EXTRA_LANGUAGE,"ko-KR"); // 인식한 언어 설정
i.putExtra(RecognizerIntent.EXTRA_PROMPT, "말해주세요"); // 유저에게 보여줄 문자
/* 환경 변수 */
prefs = getActivity().getSharedPreferences("login", 0);
smartmirror_ip = prefs.getString("SMARTMIRROR_IP", "0.0.0.0");
btn_userSpeech.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.userSpeech) {
mRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
mRecognizer.setRecognitionListener(listener);
i = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); // Intent 생성
i.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getActivity().getPackageName()); // 호출한 패키지
i.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "ko-KR"); // 인식한 언어 설정
i.putExtra(RecognizerIntent.EXTRA_PROMPT, "말해주세요"); // 유저에게 보여줄 문자
mRecognizer.startListening(i);
}
}
});
btn_stopRecog.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mRecognizer.destroy();
}
});
return rootView;
}
private RecognitionListener listener = new RecognitionListener() {
/** 음성인식 준비 완료 */
@Override
public void onReadyForSpeech(Bundle params) {
//Toast.makeText(getActivity(),"SST Ready",Toast.LENGTH_SHORT).show();
}
/** 음성인식 시작 */
@Override
public void onBeginningOfSpeech() {
Toast.makeText(getActivity(),"음성 인식 시작",Toast.LENGTH_SHORT).show();
}
/** 입력 소리 변경 시 */
@Override
public void onRmsChanged(float rmsdB) {
}
/** 더 많은 소리를 받을 경우 */
@Override
public void onBufferReceived(byte[] buffer) {
}
/** 음성인식 끝남 */
@Override
public void onEndOfSpeech() {
//Toast.makeText(getActivity(),"SST Finish",Toast.LENGTH_SHORT).show();
}
/** 에러 발생 */
@Override
public void onError(int error) {
//Toast.makeText(getActivity(),"SST Retry :D",Toast.LENGTH_SHORT).show();
mRecognizer.destroy();
mRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
mRecognizer.setRecognitionListener(listener);
mRecognizer.startListening(i);
}
/** 음성인식 결과 받음 */
@Override
public void onResults(Bundle results) {
String key = "";
key = SpeechRecognizer.RESULTS_RECOGNITION;
input_sst = results.getStringArrayList(key).get(0);
if(input_sst.equals("정지")) {
Toast.makeText(getActivity(),"음성 명령이 정지 되었습니다",Toast.LENGTH_LONG).show();
mRecognizer.destroy();
input_sst = "";
return;
}
tv_speech.setText("" + input_sst);
Toast.makeText(getActivity(),"말씀하신 명령 : "+input_sst,Toast.LENGTH_SHORT).show();
Log.d("SST : ", input_sst);
try {
enCommand = URLEncoder.encode(input_sst, "UTF-8");
/* AsyncTask*/
sendComandTask = new SendComandTask();
sendComandTask.execute();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
mRecognizer.destroy();
mRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
mRecognizer.setRecognitionListener(listener);
mRecognizer.startListening(i);
}
/** 음성인식 결과 일부 유효 */
@Override
public void onPartialResults(Bundle partialResults) {
String key = "";
key = SpeechRecognizer.RESULTS_RECOGNITION;
String input_sst = partialResults.getStringArrayList(key).get(0);
tv_speech.setText("" + input_sst);
Toast.makeText(getActivity(),"You said : "+input_sst,Toast.LENGTH_SHORT).show();
}
@Override
public void onEvent(int eventType, Bundle params) {
}
};
class SendComandTask extends AsyncTask<Void, Void, Void> {
/**
* Parameter
* - 1 parmas는 입력
* - 2 progress
* - 3 result는 리턴 값 */
@Override
protected void onPreExecute() {
/* Thread의 Run과 같은 기능, Thread로 동작 */
super.onPreExecute();
}
@Override
protected Void doInBackground(Void... voids) {
/** 라즈베리 파이2 전송하는 부분 */
try {
/* 라즈베리 파이 IP : 9090 (port) */
String urlRasp = "http://"+smartmirror_ip+":9090/android.do?command="+enCommand;
Log.d("smartmirror url : ",urlRasp);
URL url = new URL(urlRasp);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String response = null;
while (true) {
response = br.readLine();
if (response == null) break;
Log.d("[android command]","Response : "+response);
}
urlConnection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
}
}
SetIPFragment.java
앱에서 스마트 미러의 ip를 동일시 하기 위한 기능입니다.
사용자는 스마트 미러를 동작하면 명령 메뉴 부분에서 ip를 확인할 수 있으며,
앱을 이용할 경우 이 Fragment에서 ip를 설정해주어야 합니다.
192.168.0.42 와 같이 XXX.XXX.XXX.XXX으로 작성해야 됩니다.
ip가 다르다면 웹서버로 데이터 전송이나 통신이 안됩니다.
package softwarek.kimjungchul.smartmirror;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class SetIPFragment extends Fragment {
private EditText et_ip;
private TextView tv_currentIP;
private Button btn_setIP;
private String current_ip; // 환경변수에 저장된 ip주소
private String smartmirror_ip; // 스마트 미러의 할당받은 ip 주소
/* 환경 변수 */
private SharedPreferences prefs;
private SharedPreferences.Editor editor;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_setip, container, false);
tv_currentIP = (TextView)rootView.findViewById(R.id.tv_currentIP);
et_ip = (EditText)rootView.findViewById(R.id.et_ip);
btn_setIP = (Button) rootView.findViewById(R.id.btn_setIP);
prefs = getActivity().getSharedPreferences("login", 0);
editor = prefs.edit();
current_ip = prefs.getString("SMARTMIRROR_IP","0.0.0.0");
tv_currentIP.setText(current_ip);
btn_setIP.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
smartmirror_ip = et_ip.getText().toString();
editor.putString("SMARTMIRROR_IP",smartmirror_ip);
editor.commit();
Toast.makeText(getActivity(),"IP가"+ smartmirror_ip +"로 설정되었습니다.", Toast.LENGTH_SHORT).show();
}
});
return rootView;
}
}
SmartMirrorFragment.java
스마트 미러 개발자에 대한 내용입니다.
package softwarek.kimjungchul.smartmirror;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class SmartMirrorFragment extends Fragment{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_smartmirror, container, false);
return rootView;
}
}
레이아웃에 XML 파일에 대한 설명은 Github에 올라가 있는 소스를 참고해 주시면 감사하겠습니다.
이상 앱에 대한 포스팅을 마치겠습니다. 감사합니다 :D
'스마트미러' 카테고리의 다른 글
Smart Mirror 스마트미러 근황 (6) | 2017.06.11 |
---|---|
SAMSUNG ARTIK with Smart Mirror (1) (3) | 2016.09.22 |
Smart Mirror 스마트 미러 만들기- 7 발표 PPT (9) | 2016.04.17 |
Smart Mirror 스마트 미러 만들기- 6 소프트웨어 - 미러 프로그램 편 (51) | 2016.04.17 |
Smart Mirror 스마트 미러 만들기- 5 미러 하드웨어 제작 (16) | 2016.04.04 |