입력 및 센서

Android SDK를 사용하면 Glass EE2에서 사용할 수 있는 다양한 입력 및 센서에 액세스할 수 있습니다. 이 페이지에서는 사용 가능한 기능, 구현 세부정보, 유용한 팁을 간략하게 소개합니다.

터치 동작

Android SDK를 사용하여 Glass 터치패드의 원시 데이터에 액세스할 수 있습니다. 탭, 플링, 스크롤과 같이 Glass에서 일반적인 동작을 자동으로 감지하는 동작 감지기를 통해 실행됩니다.

앱에서 이 동작 감지기를 사용하여 탭, 앞으로 스와이프, 뒤로 스와이프, 아래로 스와이프를 적용할 수도 있습니다. 이전 Glass 기기와 유사합니다.

이러한 동작은 다음과 같은 방식으로 사용하는 것이 가장 좋습니다.

  • : 확인하거나 입력합니다.
  • 앞으로 스와이프, 뒤로 스와이프: 카드와 화면을 탐색합니다.
  • 아래로 스와이프: 뒤로 또는 종료

구현 세부정보는 샘플 동작 감지기를 참고하세요.

Android 동작 감지기로 동작 감지

Android GestureDetector를 사용하면 여러 손가락이나 스크롤을 사용하는 동작과 같은 단순하고 복잡한 동작을 감지할 수 있습니다.

활동 수준 동작 감지

UI의 어느 부분에 포커스가 있는지와 상관없이 활동 수준에서만 동작을 감지합니다. 예를 들어 사용자가 터치패드를 탭할 때 메뉴를 불러오려면 어떤 뷰에 포커스가 있는지와 상관없이 활동 내에서 MotionEvent을 처리합니다.

다음은 GestureDetector를 사용하고 인식된 동작을 처리하기 위해 GestureDetector.OnGestureListener를 구현하는 활동 수준 동작 감지의 예입니다. 그런 다음 이러한 동작이 처리되어 다음과 같이 번역됩니다.

  • TAP
  • SWIPE_FORWARD
  • SWIPE_BACKWARD
  • SWIPE_UP
  • SWIPE_DOWN

Kotlin

class GlassGestureDetector(context: Context, private val onGestureListener: OnGestureListener) :
    GestureDetector.OnGestureListener {

    private val gestureDetector = GestureDetector(context, this)

    enum class Gesture {
        TAP,
        SWIPE_FORWARD,
        SWIPE_BACKWARD,
        SWIPE_UP,
        SWIPE_DOWN
    }

    interface OnGestureListener {
        fun onGesture(gesture: Gesture): Boolean
    }

    fun onTouchEvent(motionEvent: MotionEvent): Boolean {
        return gestureDetector.onTouchEvent(motionEvent)
    }

    override fun onDown(e: MotionEvent): Boolean {
        return false
    }

    override fun onShowPress(e: MotionEvent) {}

    override fun onSingleTapUp(e: MotionEvent): Boolean {
        return onGestureListener.onGesture(Gesture.TAP)
    }

    override fun onScroll(
        e1: MotionEvent,
        e2: MotionEvent,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        return false
    }

    override fun onLongPress(e: MotionEvent) {}

    /**
     * Swipe detection depends on the:
     * - movement tan value,
     * - movement distance,
     * - movement velocity.
     *
     * To prevent unintentional SWIPE_DOWN and SWIPE_UP gestures, they are detected if movement
     * angle is only between 60 and 120 degrees.
     * Any other detected swipes, will be considered as SWIPE_FORWARD and SWIPE_BACKWARD, depends
     * on deltaX value sign.
     *
     * ______________________________________________________________
     * |                     \        UP         /                    |
     * |                       \               /                      |
     * |                         60         120                       |
     * |                           \       /                          |
     * |                             \   /                            |
     * |  BACKWARD  <-------  0  ------------  180  ------>  FORWARD  |
     * |                             /   \                            |
     * |                           /       \                          |
     * |                         60         120                       |
     * |                       /               \                      |
     * |                     /       DOWN        \                    |
     * --------------------------------------------------------------
     */
    override fun onFling(
        e1: MotionEvent,
        e2: MotionEvent,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        val deltaX = e2.x - e1.x
        val deltaY = e2.y - e1.y
        val tan =
            if (deltaX != 0f) abs(deltaY / deltaX).toDouble() else java.lang.Double.MAX_VALUE

        return if (tan > TAN_60_DEGREES) {
            if (abs(deltaY) < SWIPE_DISTANCE_THRESHOLD_PX || Math.abs(velocityY) < SWIPE_VELOCITY_THRESHOLD_PX) {
                false
            } else if (deltaY < 0) {
                onGestureListener.onGesture(Gesture.SWIPE_UP)
            } else {
                onGestureListener.onGesture(Gesture.SWIPE_DOWN)
            }
        } else {
            if (Math.abs(deltaX) < SWIPE_DISTANCE_THRESHOLD_PX || Math.abs(velocityX) < SWIPE_VELOCITY_THRESHOLD_PX) {
                false
            } else if (deltaX < 0) {
                onGestureListener.onGesture(Gesture.SWIPE_FORWARD)
            } else {
                onGestureListener.onGesture(Gesture.SWIPE_BACKWARD)
            }
        }
    }

    companion object {

        private const val SWIPE_DISTANCE_THRESHOLD_PX = 100
        private const val SWIPE_VELOCITY_THRESHOLD_PX = 100
        private val TAN_60_DEGREES = tan(Math.toRadians(60.0))
    }
}

자바

  public class GlassGestureDetector implements GestureDetector.OnGestureListener {

   enum Gesture {
     TAP,
     SWIPE_FORWARD,
     SWIPE_BACKWARD,
     SWIPE_UP,
     SWIPE_DOWN,
   }

   interface OnGestureListener {
     boolean onGesture(Gesture gesture);
   }

   private static final int SWIPE_DISTANCE_THRESHOLD_PX = 100;
   private static final int SWIPE_VELOCITY_THRESHOLD_PX = 100;
   private static final double TAN_60_DEGREES = Math.tan(Math.toRadians(60));

   private GestureDetector gestureDetector;
   private OnGestureListener onGestureListener;

   public GlassGestureDetector(Context context, OnGestureListener onGestureListener) {
     gestureDetector = new GestureDetector(context, this);
     this.onGestureListener = onGestureListener;
   }

   public boolean onTouchEvent(MotionEvent motionEvent) {
     return gestureDetector.onTouchEvent(motionEvent);
   }

   @Override
   public boolean onDown(MotionEvent e) {
     return false;
   }

   @Override
   public void onShowPress(MotionEvent e) {
   }

   @Override
   public boolean onSingleTapUp(MotionEvent e) {
     return onGestureListener.onGesture(Gesture.TAP);
   }

   @Override
   public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
     return false;
   }

   @Override
   public void onLongPress(MotionEvent e) {
   }

   /**
    * Swipe detection depends on the:
    * - movement tan value,
    * - movement distance,
    * - movement velocity.
    *
    * To prevent unintentional SWIPE_DOWN and SWIPE_UP gestures, they are detected if movement
    * angle is only between 60 and 120 degrees.
    * Any other detected swipes, will be considered as SWIPE_FORWARD and SWIPE_BACKWARD, depends
    * on deltaX value sign.
    *
    *           ______________________________________________________________
    *          |                     \        UP         /                    |
    *          |                       \               /                      |
    *          |                         60         120                       |
    *          |                           \       /                          |
    *          |                             \   /                            |
    *          |  BACKWARD  <-------  0  ------------  180  ------>  FORWARD  |
    *          |                             /   \                            |
    *          |                           /       \                          |
    *          |                         60         120                       |
    *          |                       /               \                      |
    *          |                     /       DOWN        \                    |
    *           --------------------------------------------------------------
    */
   @Override
   public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
     final float deltaX = e2.getX() - e1.getX();
     final float deltaY = e2.getY() - e1.getY();
     final double tan = deltaX != 0 ? Math.abs(deltaY/deltaX) : Double.MAX_VALUE;

     if (tan > TAN_60_DEGREES) {
       if (Math.abs(deltaY) < SWIPE_DISTANCE_THRESHOLD_PX || Math.abs(velocityY) < SWIPE_VELOCITY_THRESHOLD_PX) {
         return false;
       } else if (deltaY < 0) {
         return onGestureListener.onGesture(Gesture.SWIPE_UP);
       } else {
         return onGestureListener.onGesture(Gesture.SWIPE_DOWN);
       }
     } else {
       if (Math.abs(deltaX) < SWIPE_DISTANCE_THRESHOLD_PX || Math.abs(velocityX) < SWIPE_VELOCITY_THRESHOLD_PX) {
         return false;
       } else if (deltaX < 0) {
         return onGestureListener.onGesture(Gesture.SWIPE_FORWARD);
       } else {
         return onGestureListener.onGesture(Gesture.SWIPE_BACKWARD);
       }
     }
   }
  }

사용 예시

활동 수준 동작 감지를 사용하려면 다음 작업을 완료해야 합니다.

  1. 애플리케이션 선언 내부의 매니페스트 파일에 다음 선언을 추가합니다. 이렇게 하면 앱이 활동에서 MotionEvent을 수신할 수 있습니다.
    <application>
    <!-- Copy below declaration into your manifest file -->
    <meta-data
      android:name="com.google.android.glass.TouchEnabledApplication"
      android:value="true" />
    </application>
    
  2. 활동의 dispatchTouchEvent(motionEvent) 메서드를 재정의하여 동작 이벤트를 동작 감지기의 onTouchEvent(motionEvent) 메서드에 전달합니다.
  3. 활동에 GlassGestureDetector.OnGestureListener를 구현합니다.

다음은 활동 수준 동작 감지기의 예입니다.

Kotlin

class MainAcvitiy : AppCompatActivity(), GlassGestureDetector.OnGestureListener {

    private lateinit var glassGestureDetector: GlassGestureDetector

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        glassGestureDetector = GlassGestureDetector(this, this)
    }

    override fun onGesture(gesture: GlassGestureDetector.Gesture): Boolean {
        when (gesture) {
            TAP ->
                // Response for TAP gesture
                return true
            SWIPE_FORWARD ->
                // Response for SWIPE_FORWARD gesture
                return true
            SWIPE_BACKWARD ->
                // Response for SWIPE_BACKWARD gesture
                return true
            else -> return false
        }
    }

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        return if (glassGestureDetector.onTouchEvent(ev)) {
            true
        } else super.dispatchTouchEvent(ev)
    }
}

자바

  public class MainActivity extends AppCompatActivity implements OnGestureListener {

   private GlassGestureDetector glassGestureDetector;

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

     glassGestureDetector = new GlassGestureDetector(this, this);
   }

   @Override
   public boolean onGesture(Gesture gesture) {
     switch (gesture) {
       case TAP:
         // Response for TAP gesture
         return true;
       case SWIPE_FORWARD:
         // Response for SWIPE_FORWARD gesture
         return true;
       case SWIPE_BACKWARD:
         // Response for SWIPE_BACKWARD gesture
         return true;
       default:
         return false;
     }
   }

   @Override
   public boolean dispatchTouchEvent(MotionEvent ev) {
     if (glassGestureDetector.onTouchEvent(ev)) {
       return true;
     }
     return super.dispatchTouchEvent(ev);
   }
  }

오디오 입력

Glass Enterprise 버전 2는 기본 오디오 소스를 지원하는 표준 AOSP 기반 기기입니다.

다음 오디오 소스에는 고급 신호 처리가 구현되어 있습니다.

음성 인식

Glass Enterprise 버전 2는 음성 인식을 위한 기본 구현을 지원합니다. 이 기능은 영어로만 지원됩니다.

Glass 음성 인식 이미지입니다.

음성 인식 UI는 사용자가 말할 때까지 기다린 다음 스크립트 작성 텍스트를 반환합니다. 활동을 시작하려면 다음 단계를 따르세요.

  1. ACTION_RECOGNIZE_SPEECH 인텐트로 startActivityForResult()를 호출합니다. 활동을 시작할 때 다음 인텐트 추가 항목이 지원됩니다.
  2. 다음 코드 샘플과 같이 onActivityResult() 콜백을 재정의하여 EXTRA_RESULTS 인텐트 extra에서 작성된 텍스트를 수신합니다. 이 콜백은 사용자가 말하기를 완료하면 호출됩니다.

Kotlin

private const val SPEECH_REQUEST = 109

private fun displaySpeechRecognizer() {
    val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
    startActivityForResult(intent, SPEECH_REQUEST)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (requestCode == SPEECH_REQUEST && resultCode == RESULT_OK) {
        val results: List<String>? =
            data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
        val spokenText = results?.get(0)
        // Do something with spokenText.
    }
    super.onActivityResult(requestCode, resultCode, data)
}

자바

private static final int SPEECH_REQUEST = 109;

private void displaySpeechRecognizer() {
    Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    startActivityForResult(intent, SPEECH_REQUEST);
}

@Override
protected void onActivityResult(int requestCode, int resultCode,
        Intent data) {
    if (requestCode == SPEECH_REQUEST && resultCode == RESULT_OK) {
        List<String> results = data.getStringArrayListExtra(
                RecognizerIntent.EXTRA_RESULTS);
        String spokenText = results.get(0);
        // Do something with spokenText.
    }
    super.onActivityResult(requestCode, resultCode, data);
}

구현에 관한 자세한 내용은 샘플 음성 인식 앱을 참고하세요.

키워드에 대한 편향

Glass에서 음성 인식은 키워드 목록에 대해 편향될 수 있습니다. 편향은 키워드 인식 정확도를 높입니다. 키워드에 대해 편향을 활성화하려면 다음을 사용합니다.

Kotlin

val keywords = arrayOf("Example", "Biasing", "Keywords")

val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
intent.putExtra("recognition-phrases", keywords)

startActivityForResult(intent, SPEECH_REQUEST)

자바

final String[] keywords = {"Example", "Biasing", "Keywords"};

Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra("recognition-phrases", keywords);

startActivityForResult(intent, SPEECH_REQUEST);

음성 명령

음성 명령을 사용하면 사용자가 활동에서 작업을 실행할 수 있습니다. 표준 Android 메뉴 API를 사용하여 음성 명령을 빌드할 수 있지만 사용자가 터치 대신 음성 명령으로 메뉴 항목을 호출할 수 있습니다.

특정 활동에 음성 명령을 사용 설정하려면 다음 단계를 따르세요.

  1. 원하는 활동에서 getWindow().requestFeature(FEATURE_VOICE_COMMANDS)을 호출하여 음성 명령을 사용 설정합니다. 이 기능을 사용 설정하면 이 활동이 포커스를 받을 때마다 화면 왼쪽 하단에 마이크 아이콘이 표시됩니다.
  2. 앱에서 RECORD_AUDIO 권한을 요청합니다.
  3. onCreatePanelMenu()를 재정의하고 메뉴 리소스를 확장합니다.
  4. 감지된 음성 명령을 처리하도록 onContextItemSelected()를 재정의합니다.

Kotlin

class VoiceCommandsActivity : AppCompatActivity() {

    companion object {
        const val FEATURE_VOICE_COMMANDS = 14
        const val REQUEST_PERMISSION_CODE = 200
        val PERMISSIONS = arrayOf(Manifest.permission.RECORD_AUDIO)
        val TAG = VoiceCommandsActivity::class.java.simpleName
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.requestFeature(FEATURE_VOICE_COMMANDS)
        setContentView(R.layout.activity_voice_commands)

        // Requesting permissions to enable voice commands menu
        ActivityCompat.requestPermissions(
            this,
            PERMISSIONS,
            REQUEST_PERMISSION_CODE
        )
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == REQUEST_PERMISSION_CODE) {
            for (result in grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Permission denied. Voice commands menu is disabled.")
                }
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }

    override fun onCreatePanelMenu(featureId: Int, menu: Menu): Boolean {
        menuInflater.inflate(R.menu.voice_commands_menu, menu)
        return true
    }

    override fun onContextItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            // Handle selected menu item
            R.id.edit -> {
                // Handle edit action
                true
            }
            else -> super.onContextItemSelected(item)
        }
    }
}

자바

public class VoiceCommandsActivity extends AppCompatActivity {

  private static final String TAG = VoiceCommandsActivity.class.getSimpleName();
  private static final int FEATURE_VOICE_COMMANDS = 14;
  private static final int REQUEST_PERMISSION_CODE = 200;
  private static final String[] PERMISSIONS = new String[]{Manifest.permission.RECORD_AUDIO};

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().requestFeature(FEATURE_VOICE_COMMANDS);
    setContentView(R.layout.activity_voice_commands);

    // Requesting permissions to enable voice commands menu
    ActivityCompat.requestPermissions(this, PERMISSIONS, REQUEST_PERMISSION_CODE);
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_PERMISSION_CODE) {
      for (int result : grantResults) {
        if (result != PackageManager.PERMISSION_GRANTED) {
          Log.d(TAG, "Permission denied. Voice commands menu is disabled.");
        }
      }
    } else {
      super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
  }

  @Override
  public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
    getMenuInflater().inflate(R.menu.voice_commands_menu, menu);
    return true;
  }

  @Override
  public boolean onContextItemSelected(@NonNull MenuItem item) {
    switch (item.getItemId()) {
      // Handle selected menu item
      case R.id.edit:
        // Handle edit action
        return true;
      default:
        return super.onContextItemSelected(item);
    }
  }
}

다음은 이전 활동에서 사용하는 메뉴 리소스의 예입니다.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/delete"
        android:icon="@drawable/ic_delete"
        android:title="@string/delete"/>
    <item
        android:id="@+id/edit"
        android:icon="@drawable/ic_edit"
        android:title="@string/edit"/>
    <item
        android:id="@+id/find"
        android:icon="@drawable/ic_search"
        android:title="@string/find"/>
</menu>

전체 예는 샘플 메모 앱을 참조하세요.

음성 명령 목록 새로고침 중

음성 명령 목록을 동적으로 새로고침할 수 있습니다. 그렇게 하려면 onCreatePanelMenu() 메서드에서 menu 리소스를 바꾸거나 onPreparePanel() 메서드에서 메뉴 객체를 수정합니다. 변경사항을 적용하려면 invalidateOptionsMenu() 메서드를 호출합니다.

Kotlin

private val options = mutableListOf<String>()

fun onPreparePanel(featureId: Int, view: View?, menu: Menu): Boolean {
  if (featureId != FEATURE_VOICE_COMMANDS) {
    return super.onCreatePanelMenu(featureId, menu)
  }
  for (optionTitle in options) {
    menu.add(optionTitle)
  }
  return true
}

/**
 * Method showing example implementation of voice command list modification
 *
 * If you call [Activity.invalidateOptionsMenu] method, voice command  list will be
 * reloaded (onCreatePanelMenu and onPreparePanel methods will be called).
 */
private fun modifyVoiceCommandList() {
  options.add("Delete")
  options.add("Play")
  options.add("Pause")
  invalidateOptionsMenu()
}

자바

private final List<String> options = new ArrayList<>();

@Override
public boolean onPreparePanel(int featureId, View view, Menu menu) {
  if (featureId != FEATURE_VOICE_COMMANDS) {
    return super.onCreatePanelMenu(featureId, menu);
  }
  for (String optionTitle : options) {
    menu.add(optionTitle);
  }
  return true;
}

/**
 * Method showing example implementation of voice command list modification
 *
 * If you call {@link Activity#invalidateOptionsMenu()} method, voice command  list will be
 * reloaded (onCreatePanelMenu and onPreparePanel methods will be called).
 */
private void modifyVoiceCommandList() {
  options.add("Delete");
  options.add("Play");
  options.add("Pause");
  invalidateOptionsMenu();
}

AppCompatActivity 솔루션

AppCompatActivity를 확장하는 활동에서 음성 명령 목록을 다시 로드하려면 reload-voice-commands 인텐트 작업과 함께 sendBroadcast() 메서드를 사용합니다.

Kotlin

sendBroadcast(Intent("reload-voice-commands"))

자바

sendBroadcast(new Intent("reload-voice-commands"));

런타임 시 음성 명령 사용 및 사용 중지

런타임에 음성 명령을 사용 설정하거나 사용 중지할 수 있습니다. 이렇게 하려면 onCreatePanelMenu() 메서드에서 다음과 같이 적절한 값을 반환합니다.

  • 사용 설정하려면 값을 true로 설정합니다.
  • 사용 중지하려면 값을 false로 설정합니다.

디버그 모드

음성 명령의 디버그 모드를 사용 설정하려면 원하는 활동에서 getWindow().requestFeature(FEATURE_DEBUG_VOICE_COMMANDS)를 호출합니다. 디버그 모드에서는 다음 기능이 활성화됩니다.

  • logcat에는 디버깅을 위해 인식된 구문이 있는 로그가 포함되어 있습니다.
  • 다음과 같이 인식할 수 없는 명령어가 감지되면 UI 오버레이가 표시됩니다.
  • Glass 음성 인식을 인식할 수 없는 명령 이미지입니다.

Kotlin

class VoiceCommandsActivity : AppCompatActivity() {

    companion object {
        const val FEATURE_VOICE_COMMANDS = 14
        const val FEATURE_DEBUG_VOICE_COMMANDS = 15
        const val REQUEST_PERMISSION_CODE = 200
        val PERMISSIONS = arrayOf(Manifest.permission.RECORD_AUDIO)
        val TAG = VoiceCommandsActivity::class.java.simpleName
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.requestFeature(FEATURE_VOICE_COMMANDS)
        window.requestFeature(FEATURE_DEBUG_VOICE_COMMANDS)
        setContentView(R.layout.activity_voice_commands)

        // Requesting permissions to enable voice commands menu
        ActivityCompat.requestPermissions(
            this,
            PERMISSIONS,
            REQUEST_PERMISSION_CODE
        )
    }
    .
    .
    .
}

자바

public class VoiceCommandsActivity extends AppCompatActivity {

  private static final String TAG = VoiceCommandsActivity.class.getSimpleName();
  private static final int FEATURE_VOICE_COMMANDS = 14;
  private static final int FEATURE_DEBUG_VOICE_COMMANDS = 15;
  private static final int REQUEST_PERMISSION_CODE = 200;
  private static final String[] PERMISSIONS = new String[]{Manifest.permission.RECORD_AUDIO};

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().requestFeature(FEATURE_VOICE_COMMANDS);
    getWindow().requestFeature(FEATURE_DEBUG_VOICE_COMMANDS);
    setContentView(R.layout.activity_voice_commands);

    // Requesting permissions to enable voice commands menu
    ActivityCompat.requestPermissions(this, PERMISSIONS, REQUEST_PERMISSION_CODE);
  }
  .
  .
  .
}

구현에 관한 자세한 내용은 샘플 음성 명령 리로드 앱을 참고하세요.

텍스트 음성 변환 (TTS)

Text-to-Speech 기능은 디지털 텍스트를 합성된 음성 출력으로 변환합니다. 자세한 내용은 Android 개발자 문서 TextToSpeech를 참고하세요.

Glass EE2에는 Google Text-to-Speech 엔진이 설치되어 있습니다. TTS 엔진은 기본 TTS 엔진으로 설정되며 오프라인에서 작동합니다.

다음 언어는 Google Text-to-Speech 엔진과 함께 번들로 제공됩니다.

벵골어
중국어
체코어
덴마크어
독일어
그리스어
영어
스페인어
에스토니아어
핀란드어
프랑스어
구자라트어
힌디어
헝가리어
인도네시아어
이탈리아어
일본어
자바어
오스트로네시아
오스트리아
칸나다
한국어
말라얄람어
노르웨이어
네덜란드어
폴란드어
포르투갈어
러시아어
슬로바키아
순다어
스웨덴어
타밀어
텔루구어
터키어
우크라이나어
베트남어

카메라

Glass Enterprise Edition 2는 800만 화소 고정 초점 카메라와 조리개 f/2.4, 센서 가로세로 비율 4:3, 대각선 시야 83° (가로 방향 71° x 57°)를 갖추고 있습니다. 표준 CameraX 또는 Camera2 API를 사용하는 것이 좋습니다.

구현에 관한 자세한 내용은 샘플 카메라 앱을 참고하세요.

카메라 버튼

카메라 버튼은 Glass Enterprise Edition 2 기기의 힌지 부분에 있는 물리적 버튼입니다. 표준 키보드 작업처럼 처리할 수 있으며 KeyEvent#KEYCODE_CAMERA 키 코드로 식별할 수 있습니다.

OPM1.200313.001 OS 업데이트를 기준으로 다음 작업이 포함된 인텐트가 런처 애플리케이션에서 전송됩니다.

센서

개발자가 Glass EE2에서 애플리케이션을 개발할 때 사용할 수 있는 센서는 다양합니다.

Glass에서 지원되는 표준 Android 센서는 다음과 같습니다.

다음 Android 센서는 Glass에서 지원되지 않습니다.

다음 그림은 Glass 센서 좌표계를 보여줍니다. Glass 디스플레이를 기준으로 합니다. 자세한 내용은 센서 좌표계를 참고하세요.

Glass 센서 좌표계가 Glass 디스플레이를 기준으로 여기 표시됩니다.

가속도계, 자이로스코프, 자기계는 사용자가 기기를 볼 수 있도록 회전하는 Glass 기기의 광학 포드에 있습니다. 광학 포드의 각도를 직접 측정할 수는 없으므로 애플리케이션에서 나침반 방위와 같은 센서의 각도를 사용할 때는 주의가 필요합니다.

배터리 수명을 절약하려면 필요할 때만 센서를 수신하세요. 센서 듣기를 시작하거나 중지할 시점을 결정할 때 앱의 요구사항과 수명 주기를 고려하세요.

센서 이벤트 콜백은 UI 스레드에서 실행되므로 최대한 빨리 이벤트를 처리하고 반환합니다. 처리하는 데 시간이 너무 오래 걸리면 센서 이벤트를 큐로 푸시하고 백그라운드 스레드를 사용하여 처리합니다.

일반적으로 50Hz는 머리 움직임을 추적하기에 충분한 샘플링 레이트입니다.

센서를 사용하는 방법에 관한 자세한 내용은 Android 개발자 가이드를 참고하세요.

위치 서비스

Glass Enterprise Edition 2 기기는 GPS 모듈이 없으며 사용자의 위치를 제공하지 않습니다. 하지만 위치 서비스가 구현되어 있어 근처 Wi-Fi 네트워크 및 블루투스 기기 목록을 표시하는 데 필요합니다.

애플리케이션에 기기 소유자 권한이 있는 경우 이를 사용하여 프로그래매틱 방식으로 상응하는 보안 설정 값을 변경할 수 있습니다.

Kotlin

val devicePolicyManager = context
    .getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
if (devicePolicyManager.isDeviceOwnerApp(context.getPackageName())) {
    val componentName = ComponentName(context, MyDeviceAdmin::class.java)
    devicePolicyManager.setSecureSetting(
        componentName,
        Settings.Secure.LOCATION_MODE,
        Settings.Secure.LOCATION_MODE_SENSORS_ONLY.toString()
    )
}

자바

final DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context
    .getSystemService(Context.DEVICE_POLICY_SERVICE);
if (devicePolicyManager.isDeviceOwnerApp(context.getPackageName())) {
  final ComponentName componentName = new ComponentName(context, MyDeviceAdmin.class);
  devicePolicyManager.setSecureSetting(componentName, Settings.Secure.LOCATION_MODE,
      String.valueOf(Settings.Secure.LOCATION_MODE_SENSORS_ONLY));
}

타사 MDM 솔루션을 사용하는 경우 MDM 솔루션이 이러한 설정을 변경할 수 있어야 합니다.