במדריך למפתחים מוסבר איך בקר מדיניות המכשיר (DPC) יכול לנהל כמה משתמשי Android במכשירים ייעודיים.
סקירה כללית
בקר DPC יכול לעזור לכמה אנשים לחלוק מכשיר ייעודי אחד. בקר ה-DPC שלך במכשיר מנוהל יכולים ליצור ולנהל שני סוגים של משתמשים:
- משתמשים משניים הם משתמשי Android עם אפליקציות נפרדות ונתונים שמורים בין סשנים. אתם מנהלים את המשתמש באמצעות רכיב אדמין. המשתמשים האלה שימושי במקרים שבהם מרימים את המכשיר בתחילת משמרת, למשל נהגים של משלוחים או עובדי אבטחה.
- משתמשים זמניים הם משתמשים משניים שהמערכת מוחקת כשהמשתמש יפסיק לפעול, מתנתק או שהמכשיר יופעל מחדש. המשתמשים האלה שימושיים במקרים שבהם ניתן למחוק נתונים לאחר סיום הסשן, למשל, גישה ציבורית "קיוסקים" באינטרנט.
אתם משתמשים בבקר ה-DPC הקיים כדי לנהל את המכשיר הייעודי ואת משתמשים. רכיב אדמין בבקר ה-DPC מגדיר את עצמו כאדמין של רכיב משני חדש המשתמשים כשאתם יוצרים אותם.
אדמינים של משתמש משני חייבים להשתייך לאותה חבילה כמו האדמין של במכשיר שמנוהל במלואו. כדי לפשט את תהליך הפיתוח, מומלץ לשתף אדמין בין המכשיר למשתמשים המשניים.
כדי לנהל משתמשים מרובים במכשירים ייעודיים בדרך כלל נדרשת גרסת Android 9.0. עם זאת חלק מהשיטות המשמשות במדריך הזה למפתחים זמינות גרסאות קודמות של Android.
משתמשים משניים
משתמשים משניים יכולים להתחבר ל-Wi-Fi ולהגדיר רשתות חדשות. אבל הן כן לא יכולים לערוך או למחוק רשתות, אפילו לא את הרשתות שהן יצרו.
יצירת משתמשים
בקר ה-DPC יכול ליצור משתמשים נוספים ברקע ולאחר מכן להחליף אותם לחזית. התהליך כמעט זהה בחשבונות משניים משתמשים זמניים. יש לבצע את השלבים הבאים באדמינים של מכשיר מנוהל ומשתמש משני:
- קוראים לפונקציה
DevicePolicyManager.createAndManageUser()
. כדי ליצור משתמש זמני, צריך לכלולMAKE_USER_EPHEMERAL
בארגומנט הדגלים. - שיחת טלפון
DevicePolicyManager.startUserInBackground()
עד מפעילים את המשתמש ברקע. המשתמש יתחיל לפעול, אבל תצטרכו כדי לסיים את ההגדרה לפני שמעבירים את המשתמש לחזית ומציגים אותו האדם שמשתמש במכשיר. - לאדמין של המשתמש המשני, קוראים לפונקציה
DevicePolicyManager.setAffiliationIds()
עד לשייך את המשתמש החדש למשתמש הראשי. צפייה תיאום DPC בהמשך. - האדמין של המכשיר המנוהל במלואו יכול להתקשר
DevicePolicyManager.switchUser()
כדי להעביר את המשתמש אל את החזית.
בדוגמה הבאה אפשר לראות איך מוסיפים את שלב 1 ל-DPC:
Kotlin
val dpm = getContext().getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager // If possible, reuse an existing affiliation ID across the // primary user and (later) the ephemeral user. val identifiers = dpm.getAffiliationIds(adminName) if (identifiers.isEmpty()) { identifiers.add(UUID.randomUUID().toString()) dpm.setAffiliationIds(adminName, identifiers) } // Pass an affiliation ID to the ephemeral user in the admin extras. val adminExtras = PersistableBundle() adminExtras.putString(AFFILIATION_ID_KEY, identifiers.first()) // Include any other config for the new user here ... // Create the ephemeral user, using this component as the admin. try { val ephemeralUser = dpm.createAndManageUser( adminName, "tmp_user", adminName, adminExtras, DevicePolicyManager.MAKE_USER_EPHEMERAL or DevicePolicyManager.SKIP_SETUP_WIZARD) } catch (e: UserManager.UserOperationException) { if (e.userOperationResult == UserManager.USER_OPERATION_ERROR_MAX_USERS) { // Find a way to free up users... } }
Java
DevicePolicyManager dpm = (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE); // If possible, reuse an existing affiliation ID across the // primary user and (later) the ephemeral user. Set<String> identifiers = dpm.getAffiliationIds(adminName); if (identifiers.isEmpty()) { identifiers.add(UUID.randomUUID().toString()); dpm.setAffiliationIds(adminName, identifiers); } // Pass an affiliation ID to the ephemeral user in the admin extras. PersistableBundle adminExtras = new PersistableBundle(); adminExtras.putString(AFFILIATION_ID_KEY, identifiers.iterator().next()); // Include any other config for the new user here ... // Create the ephemeral user, using this component as the admin. try { UserHandle ephemeralUser = dpm.createAndManageUser( adminName, "tmp_user", adminName, adminExtras, DevicePolicyManager.MAKE_USER_EPHEMERAL | DevicePolicyManager.SKIP_SETUP_WIZARD); } catch (UserManager.UserOperationException e) { if (e.getUserOperationResult() == UserManager.USER_OPERATION_ERROR_MAX_USERS) { // Find a way to free up users... } }
כשיוצרים או מפעילים משתמש חדש, אפשר לבדוק את הסיבה לכישלון של כל משתמש
על ידי עיון בחריג UserOperationException
והתקשרות
getUserOperationResult()
חריגה מהמשתמש
המגבלות האלה הן סיבות נפוצות לכשל:
יצירת משתמש עשויה להימשך זמן מה. אם אתם יוצרים משתמשים לעיתים קרובות, תוכלו לשפר את חוויית המשתמש על ידי הכנת משתמש מוכן לפעולה ברקע. ייתכן שתצטרכו לאזן בין היתרונות של משתמש מוכן לפעולה עם מספר המשתמשים המותר במכשיר.
זיהוי
לאחר יצירת משתמש חדש, יש להפנות את המשתמש עם מספר סידורי קבוע
מספר. אל תשמרו את UserHandle
כי המערכת תמחזר אותם כמו שאתם
ליצור ולמחוק משתמשים. קבלת המספר הסידורי על ידי התקשרות
UserManager.getSerialNumberForUser()
:
Kotlin
// After calling createAndManageUser() use a device-unique serial number // (that isn’t recycled) to identify the new user. secondaryUser?.let { val userManager = getContext().getSystemService(UserManager::class.java) val ephemeralUserId = userManager!!.getSerialNumberForUser(it) // Save the serial number to storage ... }
Java
// After calling createAndManageUser() use a device-unique serial number // (that isn’t recycled) to identify the new user. if (secondaryUser != null) { UserManager userManager = getContext().getSystemService(UserManager.class); long ephemeralUserId = userManager.getSerialNumberForUser(secondaryUser); // Save the serial number to storage ... }
הגדרות משתמש
בהתאם לצרכים של המשתמשים, אפשר להתאים אישית את ההגדרה של
משתמשים. ניתן לכלול את הדגלים הבאים כשקוראים לפונקציה createAndManageUser()
:
SKIP_SETUP_WIZARD
- מדלג על הפעלת אשף ההגדרה למשתמש חדש שבודק אם יש עדכונים ומתקין אותם, מבקשת מהמשתמש להוסיף חשבון Google יחד עם שירותי Google נעילת מסך. הפעולה הזו עשויה להימשך זמן מה, וייתכן שהיא לא תהיה רלוונטית לכולם משתמשים - מכשירי קיוסק אינטרנט ציבוריים, לדוגמה.
LEAVE_ALL_SYSTEM_APPS_ENABLED
- כל אפליקציות המערכת יישארו מופעלות אצל המשתמש החדש. אם לא מגדירים את הסימון הזה, המשתמש החדש מכיל את האפליקציות המינימליות שדרושות לטלפון פועלות - בדרך כלל דפדפן קבצים, חייגן טלפון, אנשי קשר והודעות SMS.
מעקב אחר מחזור החיים של המשתמש
ה-DPC (אם מדובר בתפקיד של אדמין של המכשיר המנוהל) יכול לעזור
לדעת מתי משתמשים משניים משתנים. כדי להריץ משימות המשך אחרי שינויים, צריך לשנות את ברירת המחדל
השיטות הבאות להתקשרות חזרה במחלקה DeviceAdminReceiver
של בקר ה-DPC:
onUserStarted()
- התקשרות אחרי שהמערכת מפעילה משתמש. ייתכן שהמשתמש הזה עדיין בתהליך הגדרה או
פועלות ברקע. אפשר לקבל את שם המשתמש מ
startedUser
ארגומנט. onUserSwitched()
- נקראת אחרי שהמערכת עוברת למשתמש אחר. אפשר לקבל את המשתמש החדש
שרצה עכשיו בחזית מהארגומנט
switchedUser
. onUserStopped()
- נקראת אחרי שהמערכת מפסיקה את המשתמש כי הוא התנתק, ועבר
משתמש חדש (אם המשתמש זמני), או שה-DPC הפסיק את המשתמש. אפשר לקבל
המשתמש מהארגומנט
stoppedUser
. onUserAdded()
- נשלחת כשהמערכת מוסיפה משתמש חדש. בדרך כלל, משתמשים משניים לא
מוגדר במלואו כשה-DPC מקבל את הקריאה החוזרת (callback). אפשר לקבל את המשתמש
ארגומנט
newUser
. onUserRemoved()
- נקראת אחרי שהמערכת מוחקת משתמש. מאחר שהמשתמש כבר נמחק,
אין לך גישה למשתמש שמיוצג על ידי הארגומנט
removedUser
.
לדעת מתי המערכת מעבירה את המשתמש לחזית או שולחת אותו אל
ברקע, האפליקציות יכולות לרשום מקלט
ACTION_USER_FOREGROUND
ו-
ACTION_USER_BACKGROUND
שידורים.
גילוי משתמשים
כדי להגיע לכל המשתמשים המשניים, אדמין של מכשיר מנוהל יכול להתקשר
DevicePolicyManager.getSecondaryUsers()
התוצאות
לכלול משתמשים משניים או זמניים שהאדמין יצר. התוצאות גם
לכלול משתמשים משניים (או משתמש אורח) שמשתמש במכשיר עשוי
נוצרו. התוצאות לא כוללות פרופילים של עבודה, כי הם לא
משתמשים משניים. הדוגמה הבאה מראה איך אפשר להשתמש בשיטה הזו:
Kotlin
// The device is stored for the night. Stop all running secondary users. dpm.getSecondaryUsers(adminName).forEach { dpm.stopUser(adminName, it) }
Java
// The device is stored for the night. Stop all running secondary users. for (UserHandle user : dpm.getSecondaryUsers(adminName)) { dpm.stopUser(adminName, user); }
ריכזנו כאן עוד שיטות לבדיקת הסטטוס של משתמשים משניים:
DevicePolicyManager.isEphemeralUser()
- כדאי להפעיל את השיטה הזו מהאדמין של משתמש משני כדי לברר אם מדובר משתמש זמני.
DevicePolicyManager.isAffiliatedUser()
- כדאי להפעיל את השיטה הזו מהאדמין של משתמש משני כדי לברר אם המשתמש הזה שמשויך למשתמש הראשי. אפשר לקרוא מידע נוסף על שיוך נתונים בקטע DPC בהתאמה שלמטה.
ניהול משתמשים
אם ברצונך לנהל את מחזור החיים של המשתמש באופן מלא, אפשר לקרוא לממשקי ה-API שליטה פרטנית בזמנים ובאופן שבהם המכשיר משנה משתמשים. לדוגמה, יכולים למחוק משתמש כשלא נעשה שימוש במכשיר במשך פרק זמן מסוים, או: לשלוח לשרת את כל ההזמנות שלא נשלחו לפני שהמשמרת של אותו אדם מסתיימת.
יציאה
מערכת Android 9.0 הוסיפה לחצן התנתקות למסך הנעילה, כך שאדם שמשתמש יכול לסיים את הסשן. לאחר הקשה על הלחצן, המערכת מפסיקה את משתמש משני, מוחק את המשתמש אם הוא זמני והמשתמש הראשי חוזר לחזית. מערכת Android מסתירה את הלחצן כשהמשתמש הראשי נמצא בחזית כי המשתמש הראשי לא יכול להתנתק.
כברירת מחדל, הלחצן של סיום הסשן לא מוצג ב-Android, אבל מנהל המערכת
(מכשיר מנוהל) יכול להפעיל אותו על ידי התקשרות
DevicePolicyManager.setLogoutEnabled()
אם צריך
עליך לאשר את המצב הנוכחי של הלחצן, להתקשר
DevicePolicyManager.isLogoutEnabled()
האדמין של משתמש משני יכול באופן פרוגרמטי לנתק את המשתמש ולחזור
למשתמש הראשי. קודם כול, מוודאים שהמשתמשים המשניים והמשתמשים הראשיים
משויך, ואז קריאה ל-DevicePolicyManager.logoutUser()
. אם המיקום
המשתמש המנותק הוא משתמש זמני, המערכת מפסיקה ואז מוחקת את
משתמש.
החלפת משתמשים
כדי לעבור למשתמש משני אחר, האדמין של מכשיר מנוהל יכול
קוראים לפונקציה DevicePolicyManager.switchUser()
. לנוחיותכם,
יכול להעביר את null
כדי לעבור למשתמש הראשי.
עצירת משתמש
כדי לעצור משתמש משני, בקר DPC שבבעלותו מכשיר מנוהל יכול לבצע קריאה
DevicePolicyManager.stopUser()
אם המשתמש שעצר הוא
משתמש זמני, נעצר ואז נמחק.
מומלץ לעצור את המשתמשים ככל האפשר כדי להישאר מתחתיו המספר המקסימלי של משתמשים פעילים.
מחיקת משתמש
כדי למחוק באופן סופי משתמש משני, בקר DPC יכול לבצע אחת מהפעולות הבאות
DevicePolicyManager
אמצעי תשלום:
- אדמין של מכשיר מנוהל יכול להתקשר למספר
removeUser()
. - אדמין של המשתמש המשני יכול להתקשר למספר
wipeData()
.
המערכת מוחקת משתמשים זמניים כשהם לא מחוברים לחשבון, כשהם עוצרים או עוברים לא בקרבת מקום.
השבתה של ממשק המשתמש שמוגדר כברירת מחדל
אם בקר ה-DPC מספק ממשק משתמש לניהול המשתמשים, אפשר להשבית את
וממשק מרובה-משתמשים. אפשר לעשות זאת באמצעות שיחת טלפון
DevicePolicyManager.setLogoutEnabled()
ומוסיפים את הפקודה
ההגבלה על DISALLOW_USER_SWITCH
, כפי שמוצג
בדוגמה הבאה:
Kotlin
// Explicitly disallow logging out using Android UI (disabled by default). dpm.setLogoutEnabled(adminName, false) // Disallow switching users in Android's UI. This DPC can still // call switchUser() to manage users. dpm.addUserRestriction(adminName, UserManager.DISALLOW_USER_SWITCH)
Java
// Explicitly disallow logging out using Android UI (disabled by default). dpm.setLogoutEnabled(adminName, false); // Disallow switching users in Android's UI. This DPC can still // call switchUser() to manage users. dpm.addUserRestriction(adminName, UserManager.DISALLOW_USER_SWITCH);
מי שמשתמש במכשיר לא יכול להוסיף משתמשים משניים באמצעות ממשק המשתמש המובנה של Android.
מכיוון שאדמינים של מכשירים מנוהלים מוסיפים באופן אוטומטי את
DISALLOW_ADD_USER
הגבלת משתמשים.
הודעות סשן
כשמי שמשתמש במכשיר עובר למשתמש חדש, מערכת Android מציגה חלונית אל להדגיש את המתג. ב-Android מוצגות ההודעות הבאות:
- הודעה על התחלת סשן של משתמש שמוצגת כשהמכשיר עובר למינוי משני מהמשתמש הראשי.
- הודעה על סשן של משתמש קצה שמוצגת כשהמכשיר חוזר למשתמש הראשי ממשתמש משני.
המערכת לא מציגה את ההודעות במעבר בין שני משתמשים משניים.
מאחר שההודעות לא מתאימות לכל המצבים, ניתן לשנות בטקסט של ההודעות האלה. לדוגמה, אם הפתרון משתמש במשתמש זמני אפשר לשקף זאת בהודעות כמו: עצירת הדפדפן סשן מתבצעת מחיקה של המידע האישי...
המערכת מציגה את ההודעה למשך כמה שניות בלבד, כך שכל הודעה
צריך להיות ביטוי קצר וברור. כדי להתאים אישית את ההודעות, האדמין יכול להתקשר
השיטות DevicePolicyManager
setStartUserSessionMessage()
וגם
setEndUserSessionMessage()
כפי שמוצג
בדוגמה הבאה:
Kotlin
// Short, easy-to-read messages shown at the start and end of a session. // In your app, store these strings in a localizable resource. internal val START_USER_SESSION_MESSAGE = "Starting guest session…" internal val END_USER_SESSION_MESSAGE = "Stopping & clearing data…" // ... dpm.setStartUserSessionMessage(adminName, START_USER_SESSION_MESSAGE) dpm.setEndUserSessionMessage(adminName, END_USER_SESSION_MESSAGE)
Java
// Short, easy-to-read messages shown at the start and end of a session. // In your app, store these strings in a localizable resource. private static final String START_USER_SESSION_MESSAGE = "Starting guest session…"; private static final String END_USER_SESSION_MESSAGE = "Stopping & clearing data…"; // ... dpm.setStartUserSessionMessage(adminName, START_USER_SESSION_MESSAGE); dpm.setEndUserSessionMessage(adminName, END_USER_SESSION_MESSAGE);
כדי למחוק את ההודעות המותאמות אישית ולחזור לברירת המחדל של Android, מעבירים את null
הודעות. אם אתה צריך לבדוק את הטקסט של ההודעה הנוכחית, התקשר
getStartUserSessionMessage()
או
getEndUserSessionMessage()
בקר ה-DPC צריך להגדיר הודעות מקומיות ללוקאל הנוכחי של המשתמש. צריך גם לעדכן את ההודעות כאשר שינויים באזור של המשתמש:
Kotlin
override fun onReceive(context: Context?, intent: Intent?) { // Added the <action android:name="android.intent.action.LOCALE_CHANGED" /> // intent filter for our DeviceAdminReceiver subclass in the app manifest file. if (intent?.action === ACTION_LOCALE_CHANGED) { // Android's resources return a string suitable for the new locale. getManager(context).setStartUserSessionMessage( getWho(context), context?.getString(R.string.start_user_session_message)) getManager(context).setEndUserSessionMessage( getWho(context), context?.getString(R.string.end_user_session_message)) } super.onReceive(context, intent) }
Java
public void onReceive(Context context, Intent intent) { // Added the <action android:name="android.intent.action.LOCALE_CHANGED" /> // intent filter for our DeviceAdminReceiver subclass in the app manifest file. if (intent.getAction().equals(ACTION_LOCALE_CHANGED)) { // Android's resources return a string suitable for the new locale. getManager(context).setStartUserSessionMessage( getWho(context), context.getString(R.string.start_user_session_message)); getManager(context).setEndUserSessionMessage( getWho(context), context.getString(R.string.end_user_session_message)); } super.onReceive(context, intent); }
תיאום בקר DPC
בדרך כלל, כדי לנהל משתמשים משניים צריך ליצור שני מופעים של בקר ה-DPC – מופע אחד שבבעלותו המכשיר המנוהל במלואו ואילו המכשיר האחר הוא הבעלים המשני. במהלך היצירה משתמש חדש, האדמין של המכשיר המנוהל הגדיר מופע נוסף של עצמו כאדמין של המשתמש החדש.
משתמשים משויכים
חלק מממשקי ה-API במדריך למפתחים פועלים רק כאשר המשתמשים המשניים הם משויכים. כי מערכת Android משביתה חלק מהתכונות (למשל, רישום ביומן ברשת) כשמוסיפים משתמשים משניים חדשים לא משויכים במכשיר, עליך לשייך משתמשים בהקדם האפשרי. אפשר לראות את הדוגמה ב- הגדרה בהמשך.
הגדרה
לפני ההגדרה של משתמשים משניים חדשים (מה-DPC שהוא הבעלים של המשתמש המשני), צריך להגדיר
לאפשר לאנשים להשתמש בהם. אפשר לבצע את ההגדרה דרך
DeviceAdminReceiver.onEnabled()
קריאה חוזרת (callback). אם בעבר
להגדיר תוספות אדמין בשיחה ל-createAndManageUser()
, כדי לקבל
מהארגומנט intent
. בדוגמה הבאה מוצג בקר DPC משויך
משתמש משני חדש בקריאה החוזרת (callback):
Kotlin
override fun onEnabled(context: Context?, intent: Intent?) { super.onEnabled(context, intent) // Get the affiliation ID (our DPC previously put in the extras) and // set the ID for this new secondary user. intent?.getStringExtra(AFFILIATION_ID_KEY)?.let { val dpm = getManager(context) dpm.setAffiliationIds(getWho(context), setOf(it)) } // Continue setup of the new secondary user ... }
Java
public void onEnabled(Context context, Intent intent) { // Get the affiliation ID (our DPC previously put in the extras) and // set the ID for this new secondary user. String affiliationId = intent.getStringExtra(AFFILIATION_ID_KEY); if (affiliationId != null) { DevicePolicyManager dpm = getManager(context); dpm.setAffiliationIds(getWho(context), new HashSet<String>(Arrays.asList(affiliationId))); } // Continue setup of the new secondary user ... }
RPCs בין בקרי DPC
שתי מכונות ה-DPC פועלות אצל משתמשים נפרדים, אבל בקרי ה-DPC
שהם הבעלים של המכשיר והמשתמשים המשניים יכולים לתקשר זה עם זה.
קריאה לשירות של בקר DPC אחר חוצה את גבולות המשתמשים, ולכן ה-DPC לא יכול
להתקשר אל bindService()
כמו בדרך כלל
Android. קישור לשירות שפועל ב-
משתמש נוסף, התקשרות
DevicePolicyManager.bindDeviceAdminServiceAsUser()
בקר ה-DPC יכול לקשר רק לשירותים שפועלים אצל המשתמשים שהוחזרו על ידי
DevicePolicyManager.getBindDeviceAdminTargetUsers()
בדוגמה הבאה מוצג האדמין של משתמש משני שמקשר לאדמין
של המכשיר המנוהל:
Kotlin
// From a secondary user, the list contains just the primary user. dpm.getBindDeviceAdminTargetUsers(adminName).forEach { // Set up the callbacks for the service connection. val intent = Intent(mContext, FullyManagedDeviceService::class.java) val serviceconnection = object : ServiceConnection { override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) { // Call methods on service ... } override fun onServiceDisconnected(componentName: ComponentName) { // Clean up or reconnect if needed ... } } // Bind to the service as the primary user [it]. val bindSuccessful = dpm.bindDeviceAdminServiceAsUser(adminName, intent, serviceconnection, Context.BIND_AUTO_CREATE, it) }
Java
// From a secondary user, the list contains just the primary user. List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(adminName); if (targetUsers.isEmpty()) { // If the users aren't affiliated, the list doesn't contain any users. return; } // Set up the callbacks for the service connection. Intent intent = new Intent(mContext, FullyManagedDeviceService.class); ServiceConnection serviceconnection = new ServiceConnection() { @Override public void onServiceConnected( ComponentName componentName, IBinder iBinder) { // Call methods on service ... } @Override public void onServiceDisconnected(ComponentName componentName) { // Clean up or reconnect if needed ... } }; // Bind to the service as the primary user. UserHandle primaryUser = targetUsers.get(0); boolean bindSuccessful = dpm.bindDeviceAdminServiceAsUser( adminName, intent, serviceconnection, Context.BIND_AUTO_CREATE, primaryUser);
מקורות מידע נוספים
כדי לקבל מידע נוסף על מכשירים ייעודיים, כדאי לקרוא את המסמכים הבאים:
- סקירה כללית של מכשירים ייעודיים היא סקירה כללית של למכשירים ייעודיים.
- במצב 'נעילת משימה' מוסבר איך לנעול מכשיר ייעודי לאפליקציה אחת או לקבוצת אפליקציות אחת.
- ספר מתכונים ייעודי למכשירים עם דוגמאות נוספות להגבלת המכשירים הייעודיים ולשיפור ביצועי המשתמש חוויה אישית.