קלט וחיישנים

באמצעות ה-SDK של Android, אפשר לקבל גישה לקלט ולחיישנים השונים שזמינים עם Glass EE2. הדף הזה כולל סקירה כללית של התכונות הזמינות, פרטי ההטמעה וטיפים מועילים.

פקודות מגע

ניתן להשתמש ב-Android SDK כדי לאפשר גישה לנתונים גולמיים מלוח המגע של Glass. ניתן לעשות זאת באמצעות מזהה תנועה שמזהה באופן אוטומטי תנועות נפוצות ב-Glass כמו הקשה, הנפה וגלילה.

ניתן גם להשתמש במזהה התנועה באפליקציות שלך כדי להקיש, להחליק קדימה, להחליק חזרה והחלקה למטה. הגדרה זו דומה למכשירי Glass קודמים.

עדיף להשתמש בתנועות הבאות בדרכים הבאות:

  • מקישים על אישור או על Enter.
  • החלקה קדימה, מחליקים אחורה: מנווטים בין הכרטיסים והמסכים.
  • החלקה למטה: חזרה או יציאה.

לקבלת פרטי הטמעה, קראו את המאמר מזהה תנועה לדוגמה.

זיהוי תנועות באמצעות מזהה התנועה של Android

ב-Android GestureDetector אפשר לזהות תנועות פשוטות ומורכבות, כמו תנועות שכוללות מספר אצבעות או גלילה.

זיהוי תנועות ברמת הפעילות

זהה תנועות ברמת הפעילות רק כאשר אין חשיבות לחלק של ממשק המשתמש. לדוגמה, אם רוצים להציג תפריט כשמשתמשים מקישים על לוח המגע, לא משנה באיזו תצוגה מתמקדים, צריך להתייחס 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))
    }
}

Java

  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)
    }
}

Java

  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 Edition 2 הוא מכשיר רגיל המבוסס על AOSP שתומך במקורות אודיו בסיסיים.

במקורות האודיו הבאים מוטמע עיבוד אותות מתקדם:

זיהוי קולי

Glass Enterprise Edition 2 תומכת ביישום מקורי לזיהוי דיבור. תכונה זו נתמכת באנגלית בלבד.

תמונה של זיהוי קול ב-Glass.

ממשק המשתמש לזיהוי דיבור ממתין למשתמשים לדבר, ולאחר מכן מחזיר את הטקסט המתומלל. כדי להתחיל בפעילות, פועלים לפי השלבים הבאים:

  1. התקשרות ל-startActivityForResult() באמצעות כוונת ACTION_RECOGNIZE_SPEECH. כשמגדירים את הפעילות, המערכת תומכת בתוספות הבאות לכוונת הרכישה:
  2. יש לשנות את הקריאה החוזרת (callback) onActivityResult() כדי לקבל את הטקסט המתומלל מכוונת EXTRA_RESULTS כפי שמוצג בדוגמת הקוד הבאה. השיחה החוזרת הזו מופעלת כשהמשתמש סיים לדבר.

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)
}

Java

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)

Java

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

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

startActivityForResult(intent, SPEECH_REQUEST);

פקודות קוליות

פקודות קוליות מאפשרות למשתמשים לבצע פעולות מפעילות. פיתוח פקודות קוליות נעשית עם ממשקי ה-API הרגילים של תפריט Android, אך המשתמשים יכולים להפעיל את אפשרויות התפריט באמצעות פקודות קוליות במקום מגע.

להפעלת פקודות קוליות בפעילות מסוימת, מבצעים את הפעולות הבאות:

  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)
        }
    }
}

Java

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>

כדי לקבל דוגמה מלאה, יש לעיין באפליקציה לדוגמה לרישום הערות.

מתבצעת טעינה מחדש של רשימת הפקודות הקוליות

ניתן לטעון מחדש באופן דינמי את רשימת הפקודות הקוליות. לשם כך, צריך להחליף את המשאב של menu בשיטה onCreatePanelMenu() או לשנות את האובייקט של התפריט בשיטה 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()
}

Java

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, יש להשתמש בשיטה sendBroadcast() עם פעולת הכוונת reload-voice-commands:

Kotlin

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

Java

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

הפעלה והשבתה של פקודות קוליות בזמן ריצה

אפשר להפעיל ולהשבית פקודות קוליות בזמן ריצה. כדי לעשות את זה, צריך להחזיר את הערך המתאים מהשיטה onCreatePanelMenu():

  • יש להגדיר את הערך ל-true כדי להפעיל אותו.
  • יש להגדיר את הערך ל-false כדי להשבית אותו.

מצב ניפוי באגים

כדי להפעיל מצב ניפוי באגים בפקודות קוליות, צריך להתקשר getWindow().requestFeature(FEATURE_DEBUG_VOICE_COMMANDS) לפעילות הרצויה. מצב ניפוי באגים מפעיל את התכונות הבאות:

  • Logcat מכיל את היומן שבו הביטוי המזוהה לניפוי באגים.
  • שכבת-על של ממשק משתמש מוצגת כשמזוהה פקודה לא מזוהה, כפי שמוצג:
  • תמונת פקודה לא מזוהה של 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
        )
    }
    .
    .
    .
}

Java

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)

הפונקציונליות של המרת טקסט לדיבור (TTS) ממירה טקסט דיגיטלי לפלט דיבור מאוחד. למידע נוסף, היכנסו אל תיעוד טקסט לדיבור של מפתחי Android.

ב-Glass EE2 מותקן המרת טקסט לדיבור של Google. הוא מוגדר כמנוע ה-TTS המוגדר כברירת מחדל, והוא פועל במצב אופליין.

הלוקאלים הבאים כלולים במנוע המרת טקסט לדיבור של Google:

בנגלית
מנדרינית
צ'כית
דנית
גרמנית
אנגלית
אנגלית
ספרדית
אסטונית
פינית
צרפתית
גוג'ראטית
הינדית
הונגרית
אינדונזית
איטלקית
יפנית
ג'אווה
אוסטרית
אוסטרואסטית
קנאדה
קוריאנית
מליאלאם
נורווגית
הולנדית
הולנדית
פולנית
פורטוגזית
רוסית
סלובקית
שוודית
טמילית
טלוגו
תאית
טורקית
אוקראינית
וייטנאמית

מצלמה

Glass Enterprise Edition 2 מצויד במצלמת 8 מגה-פיקסל, עם פוקוס קבוע, עם מיפתח צמצם של f/2.4, יחס גובה-רוחב של חיישן 4:3 ושדה ראייה של 83° בכיוון התצוגה לרוחב (71° x 57°). מומלץ להשתמש ב-API הרגיל של CameraX או Camera2.

מידע על ההטמעה זמין באפליקציה לדוגמה למצלמה.

לחצן המצלמה

לחצן המצלמה הוא הלחצן הפיזי שעל ציר המכשיר של Glass Enterprise Edition 2. ניתן לטפל בו בדיוק כמו פעולת מקלדת רגילה, וניתן לזהות אותו לפי קוד המפתח KeyEvent#KEYCODE_CAMERA.

החל מעדכון OPM1.200313.001, כוונות עם הפעולות הבאות נשלחות מהאפליקציה של מרכז האפליקציות:

חיישנים

קיים מגוון של חיישנים הזמינים למפתחים כשהם מפתחים אפליקציות ב-Glass EE2.

חיישני Android הרגילים הבאים נתמכים ב-Glass:

חיישני Android הבאים לא נתמכים ב-Glass:

מערכת הקואורדינטות של חיישן הזכוכית מוצגת באיור הבא. הוא ביחס למסך הזכוכית. למידע נוסף, אפשר לעיין במאמר מערכת הקואורדינטות של החיישן.

מערכת הקואורדינטות של חיישן הזכוכית מוצגת כאן, ביחס למסך הזכוכית.

המד התאוצה, הג'ירוסקופ והמגנטומטר ממוקמים על מוט האופטיקה של מכשיר ה-Glass, שהמשתמשים מסובבים כדי ליישר את המכשיר מול הראייה שלהם. לא ניתן למדוד ישירות את הזווית של מוט האופטיקה, ולכן חשוב לשים לב כשמשתמשים בזוויות מהחיישנים האלה לאפליקציות, כמו כותרת של מצפן.

כדי להאריך את חיי הסוללה, מומלץ להאזין לחיישנים רק כשיש צורך בהם. צריך להביא בחשבון את הצרכים ואת מחזור החיים של האפליקציה כאשר מחליטים מתי להתחיל ולהפסיק להאזין לחיישנים.

התכונה 'התקשרות חזרה לאירועים בחיישן' פועלת בשרשור של ממשק המשתמש. לכן, אפשר לעבד אירועים והחזרות במהירות האפשרית. אם העיבוד נמשך יותר מדי זמן, יש לדחוף אירועי חיישן לתור ולהשתמש בשרשור ברקע כדי לטפל בהם.

50 Hz הוא לרוב תדירות דגימה מספקת למעקב אחר תנועת הראש.

למידע נוסף על השימוש בחיישנים, אפשר לעיין במדריך למפתחים של Android.

שירותי מיקום

מכשיר Glass Enterprise Edition 2 לא מצויד במודול GPS, והוא לא מספק את מיקום המשתמש. אבל יש בו שירותי מיקום מוטמעים, שדרושים כדי להציג רשימה של רשתות Wi-Fi ומכשירי Bluetooth בקרבת מקום.

אם לאפליקציה יש הרשאות של בעל מכשיר, אפשר להשתמש בה כדי לשנות באופן פרוגרמטי את הערך של ההגדרה המאובטחת המתאימה:

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()
    )
}

Java

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 (ניהול מכשירים ניידים) יכול לשנות את ההגדרות האלה בשבילך.