מקורות קלט וחיישנים

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

פקודות מגע

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

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

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

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

פרטים נוספים מופיעים בגלאי מחוות לדוגמה.

זיהוי תנועות באמצעות הכלי לזיהוי תנועות ב-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. מחליפים את ה-method של הפעילות dispatchTouchEvent(motionEvent) כדי להעביר את אירועי התנועה אל ה-method של גלאי התנועות 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() כדי לקבל את הטקסט המתומלל מה-intent extra‏ 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. ‫Override onCreatePanelMenu() ולהרחיב משאב של תפריט.
  4. ‫Override 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"));

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

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

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

ב-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, מנגנוני Intent עם הפעולות הבאות נשלחים מאפליקציית מרכז האפליקציות:

חיישנים

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

‫Glass תומך בחיישני Android הסטנדרטיים הבאים:

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

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

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

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

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

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

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

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