Подтвердить что ты не робот

Приложение для мониторинга других приложений на Android

Проблема под рукой: Я должен создать Service, который работает непрерывно. Эта служба отслеживает, что 5 приложений говорят, что на вашем телефоне установлено 5 игр для Android. Эта служба должна получить информацию о:  1. Сколько раз игра открывается и запускается?  2. Сколько времени играла каждая игра.

например: Say Если у меня эта служба установлена ​​в моем приложении. И я отпустил его на месяц. Мне нужна информация такого рода на экране приложения:

Игра Количество запусков игры Продолжительность игры

Игра 1 20 раз в общей сложности играла 15 часов

Игра 2 16 раз в общей сложности играла 25 часов

..

..

Игра 5 10 раз в общей сложности играла 12 часов

Возможный подход: Когда приложение загружается, оно поступает в память. Отмечая время системных часов при запуске приложения. И когда приложение заканчивается или помещается в фоновом режиме, отмечая время снова.

Скажите, если приложение будет доставлено в память в 21:00 и выйдет на второй план в 9:30 вечера, что даст нам игровое время продолжительностью 30 минут. в следующий раз, когда приложение будет исполнено, продолжительность будет добавлена ​​к 30 из предыдущего воспроизведения, сохраненного в какой-то переменной и так далее. Каждый раз, когда приложение вводится в память, счетчик его воспроизведения должен увеличиваться на единицу. Если мы даем нам количество раз, когда приложение воспроизводится.

Coding: Я понятия не имею о Service в Android, поскольку я никогда на них не работал. Любые учебники, связанные с моей проблемой, будут очень полезны. Во-вторых, если есть другой способ, которым это можно было бы сделать. Я тоже хотел бы это знать. Я мог бы действительно использовать фрагмент кода для запуска этого проекта.

4b9b3361

Ответ 1

Как вы писали, задача состоит в том, чтобы отслеживать 3-сторонние сторонние приложения, нет другого решения, кроме как периодически просматривать список процессов и обнаруживать процесс переднего плана. Для этого вам нужна услуга. К сожалению, Android не предоставляет таких средств, как широковещательные события для изменения процесса переднего плана.

Задача требует большого количества кода на самом деле, по крайней мере, гораздо больше, чем обычный ответ. Я размещаю часть его здесь, но вы должны учитывать многие нюансы, оставленные за кулисами, такие как синхронизация и постоянная информация между запусками. Это всего лишь скелет.

Во-первых, разрешите код для объекта приложения, что является хорошим местом для регистрации всех материалов, связанных с экземплярами.

MonitorApp

public class MonitorApp extends Application
{
  // actual store of statistics
  private final ArrayList<HashMap<String,Object>> processList = new ArrayList<HashMap<String,Object>>();

  // fast-access index by package name (used for lookup)
  private ArrayList<String> packages = new ArrayList<String>();

  public ArrayList<HashMap<String,Object>> getProcessList()
  {
    return processList;
  }

  public ArrayList<String> getPackages()
  {
    return packages;
  }

  // TODO: you need to save and load the instance data
  // TODO: you need to address synchronization issues
}

Затем проецирует действие.

MonitorActivity

import static ProcessList.COLUMN_PROCESS_NAME;
import static ProcessList.COLUMN_PROCESS_PROP;
import static ProcessList.COLUMN_PROCESS_COUNT;
import static ProcessList.COLUMN_PROCESS_TIME;

public class MonitorActivity extends Activity implements MonitorService.ServiceCallback
{
  private ArrayList<HashMap<String,Object>> processList;
  private MonitorService backgroundService;
  private MyCustomAdapter adapter = null;
  private ListView listView = null;

  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main); // TODO: provide your layout
    listView = (ListView)findViewById(R.id.id_process_listview);
    createAdapter();

    this.bindService(
      new Intent(this, MonitorService.class),
      serviceConnection,
      Context.BIND_AUTO_CREATE);
  }

  private void createAdapter()
  {
    processList = ((MonitorApp)getApplication()).getProcessList();
    adapter = new MyCustomAdapter(this, processList, R.layout.complex_list_item,
    new String[]
    {
      COLUMN_PROCESS_NAME,
      COLUMN_PROCESS_PROP, // TODO: you may calculate and pre-fill this field
                           // from COLUMN_PROCESS_COUNT and COLUMN_PROCESS_TIME
                           // so eliminating the need to use the custom adapter
    },
    new int[]
    {
      android.R.id.text1,
      android.R.id.text2
    });

    listView.setAdapter(adapter);
  }

  // callback method invoked by the service when foreground process changed
  @Override
  public void sendResults(int resultCode, Bundle b)
  {
    adapter.notifyDataSetChanged();
  }

  private class MyCustomAdapter extends SimpleAdapter
  {
    MyCustomAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
    {
      super(context, data, resource, from, to);
    }

    @Override
    public View getView (int position, View convertView, ViewGroup parent)
    {
      View result = super.getView(position, convertView, parent);

      // TODO: customize process statistics display
      int count = (Integer)(processList.get(position).get(COLUMN_PROCESS_COUNT));
      int seconds = (Integer)(processList.get(position).get(COLUMN_PROCESS_TIME));

      return result;
    }
  }

  private ServiceConnection serviceConnection = new ServiceConnection()
  {
    @Override
    public void onServiceConnected(ComponentName className, IBinder service)
    {
      LocalBinder binder = (LocalBinder)service;
      backgroundService = binder.getService();
      backgroundService.setCallback(MonitorActivity.this);
      backgroundService.start();
    }

    @Override
    public void onServiceDisconnected(ComponentName className)
    {
      backgroundService = null;
    }
  };

  @Override
  public void onResume()
  {
    super.onResume();
    if(backgroundService != null)
    {
      backgroundService.setCallback(this);
    }
  }

  @Override
  public void onPause()
  {
    super.onPause();
    if(backgroundService != null)
    {
      backgroundService.setCallback(null);
    }
  }

}

Активность запускает службу рабочего стола, которая фактически контролирует процессы. Вы могли бы переместить регистрацию службы из этой операции в экземпляр приложения. Сама услуга выглядит примерно так:

MonitorService

public class MonitorService extends Service
{
  private boolean initialized = false;
  private final IBinder mBinder = new LocalBinder();
  private ServiceCallback callback = null;
  private Timer timer = null;
  private final Handler mHandler = new Handler();
  private String foreground = null;
  private ArrayList<HashMap<String,Object>> processList;
  private ArrayList<String> packages;
  private Date split = null;

  public static int SERVICE_PERIOD = 5000; // TODO: customize (this is for scan every 5 seconds)

  private final ProcessList pl = new ProcessList(this)
  {
    @Override
    protected boolean isFilteredByName(String pack)
    {
      // TODO: filter processes by names, return true to skip the process
      // always return false (by default) to monitor all processes
      return false;
    }

  };

  public interface ServiceCallback
  {
    void sendResults(int resultCode, Bundle b);
  }

  public class LocalBinder extends Binder
  {
    MonitorService getService()
    {
      // Return this instance of the service so clients can call public methods
      return MonitorService.this;
    }
  }

  @Override
  public void onCreate()
  {
    super.onCreate();
    initialized = true;
    processList = ((MonitorApp)getApplication()).getProcessList();
    packages = ((MonitorApp)getApplication()).getPackages();
  }

  @Override
  public IBinder onBind(Intent intent)
  {
    if(initialized)
    {
      return mBinder;
    }
    return null;
  }

  public void setCallback(ServiceCallback callback)
  {
    this.callback = callback;
  }

  private boolean addToStatistics(String target)
  {
    boolean changed = false;
    Date now = new Date();
    if(!TextUtils.isEmpty(target))
    {
      if(!target.equals(foreground))
      {
        int i;
        if(foreground != null && split != null)
        {
          // TODO: calculate time difference from current moment
          // to the moment when previous foreground process was activated
          i = packages.indexOf(foreground);
          long delta = (now.getTime() - split.getTime()) / 1000;
          Long time = (Long)processList.get(i).get(COLUMN_PROCESS_TIME);
          if(time != null)
          { 
            // TODO: add the delta to statistics of 'foreground' 
            time += delta;
          }
          else
          {
            time = new Long(delta);
          }
          processList.get(i).put(COLUMN_PROCESS_TIME, time);
        }

        // update count of process activation for new 'target'
        i = packages.indexOf(target);
        Integer count = (Integer)processList.get(i).get(COLUMN_PROCESS_COUNT);
        if(count != null) count++;
        else
        {
          count = new Integer(1);
        }
        processList.get(i).put(COLUMN_PROCESS_COUNT, count);

        foreground = target;
        split = now;
        changed = true;
      }
    }
    return changed; 
  }


  public void start()
  {
    if(timer == null)
    {
      timer = new Timer();
      timer.schedule(new MonitoringTimerTask(), 500, SERVICE_PERIOD);
    }

    // TODO: startForeground(srvcid, createNotification(null));
  }

  public void stop()
  {
    timer.cancel();
    timer.purge();
    timer = null;
  }

  private class MonitoringTimerTask extends TimerTask
  {
    @Override
    public void run()
    {
      fillProcessList();

      ActivityManager activityManager = (ActivityManager)MonitorService.this.getSystemService(ACTIVITY_SERVICE);
      List<RunningTaskInfo> taskInfo = activityManager.getRunningTasks(1);
      String current = taskInfo.get(0).topActivity.getPackageName();

      // check if current process changed
      if(addToStatistics(current) && callback != null)
      {
        final Bundle b = new Bundle();
        // TODO: pass necessary info to UI via bundle
        mHandler.post(new Runnable()
        {
          public void run()
          {
            callback.sendResults(1, b);
          }
        });
      }
    }
  }

  private void fillProcessList()
  {
    pl.fillProcessList(processList, packages);
  }

}

Служба использует вспомогательный класс для создания списков процессов.

PROCESSLIST

public abstract class ProcessList
{
  // process package name
  public static final String COLUMN_PROCESS_NAME = "process";

  // TODO: arbitrary property (can be user-fiendly name)
  public static final String COLUMN_PROCESS_PROP = "property";

  // number of times a process has been activated
  public static final String COLUMN_PROCESS_COUNT = "count";

  // number of seconds a process was in foreground
  public static final String COLUMN_PROCESS_TIME = "time";

  private ContextWrapper context;

  ProcessList(ContextWrapper context)
  {
    this.context = context;
  }

  protected abstract boolean isFilteredByName(String pack);

  public void fillProcessList(ArrayList<HashMap<String,Object>> processList, ArrayList<String> packages)
  {
    ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningAppProcessInfo> procInfo = activityManager.getRunningAppProcesses();

    HashMap<String, Object> hm;
    final PackageManager pm = context.getApplicationContext().getPackageManager();

    for(int i = 0; i < procInfo.size(); i++)
    {
      String process = procInfo.get(i).processName;
      String packageList = Arrays.toString(procInfo.get(i).pkgList);
      if(!packageList.contains(process))
      {
        process = procInfo.get(i).pkgList[0];
      }

      if(!packages.contains(process) && !isFilteredByName(process))
      {
        ApplicationInfo ai;
        String applicationName = "";

        for(int k = 0; k < procInfo.get(i).pkgList.length; k++)
        {
          String thisPackage = procInfo.get(i).pkgList[k];
          try
          {
            ai = pm.getApplicationInfo(thisPackage, 0);
          }
          catch(final NameNotFoundException e)
          {
            ai = null;
          }
          if(k > 0) applicationName += " / ";
          applicationName += (String)(ai != null ? pm.getApplicationLabel(ai) : "(unknown)");
        }

        packages.add(process);
        hm = new HashMap<String, Object>();
        hm.put(COLUMN_PROCESS_NAME, process);
        hm.put(COLUMN_PROCESS_PROP, applicationName);
        processList.add(hm);
      }
    }

    // optional sorting
    Comparator<HashMap<String, Object>> comparator = new Comparator<HashMap<String, Object>>()
    {
      public int compare(HashMap<String, Object> object1, HashMap<String, Object> object2) 
      {       
        return ((String)object1.get(COLUMN_PROCESS_NAME)).compareToIgnoreCase((String)object2.get(COLUMN_PROCESS_NAME));
      }
    };
    Collections.sort(processList, comparator);

    packages.clear();
    for(HashMap<String, Object> e : processList)
    {
      packages.add((String)e.get(COLUMN_PROCESS_NAME));
    }
  }

}

Наконец, манифест.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yourpackage"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.GET_TASKS" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".MonitorActivity"
            android:label="@string/app_name"
            android:configChanges="orientation|keyboardHidden" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".MonitorService" />
    </application>

</manifest>

Как вы можете видеть, в нем уже много кода. Он частично извлечен из рабочего приложения, но я сделал быстрые изменения для ваших нужд, поэтому могут быть опечатки, все импорт пропущен и т.д. Тем не менее, я надеюсь, что это немного помогает.

ДОБАВЛЕНИЕ: Lollipop +

Остерегайтесь: последние версии Android нарушили вышеупомянутый подход. Вот что официальная документация говорит о методе getRunningTasks и других:

Как и в случае с LOLLIPOP, этот метод больше не доступен для сторонних приложений: введение рецентов, ориентированных на документ, означает, что он может передавать информацию об абоненте вызывающей стороне. Для обратной совместимости он по-прежнему будет восстанавливать небольшое подмножество своих данных: по крайней мере, собственные задачи вызывающего абонента и, возможно, некоторые другие задачи, например, дома, которые, как известно, не чувствительны.

Я думаю, что это перебор, и это можно сделать гораздо более избирательным и удобным способом. Не говоря уже о том, что это кажется слишком театральным, учитывая многие встроенные функции Google в отношении конфиденциальности. Во всяком случае, мы ничего не можем с этим сделать.

Единственным обходным решением является внедрение службы доступности Android (подробнее здесь и здесь) и перехватить все действия с приложениями, получающими и теряющие фокус оттуда. Пользователь должен включить службу вручную! Ваше приложение должно каким-то образом направить пользователя на это.

Ответ 2

Мои мысли,

  • Разработка приложения, которое делает некоторые из их данных общедоступными для другого процесса для доступа к ним.

    Google "Как использовать поставщиков контента".

    Каждое ваше приложение должно будет вести запись обо всех действиях, которые они выполняли. Эти данные будут общедоступными для доступа.

  • Для этого вам не нужно запускать службу. Всякий раз, когда ваше приложение мониторинга открывается. Просто нужно извлечь эти данные из всех приложений, которые вы хотите, и отобразить их соответственно.

  • В случае, когда вы хотите периодически отправлять эти данные на сервер в фоновом режиме. Затем вам понадобится услуга. Но все равно вам не нужно запускать его навсегда. Сделайте что-нибудь подобное.

    i) Из данных извлечения службы и загрузки на сервер.

    ii) Остановите службу и назначьте будильник через некоторое время T1, чтобы снова запустить свой собственный сервис, чтобы снова выполнить шаг 1.

    iii) T1 зависит от требования. Как обновить данные на сервере.

Я даже могу сказать вам указатель на фрагменты кода, если вы хотите, чтобы реализовать то, что я сказал выше. Но я предлагаю вам найти это в Google и сделать это самостоятельно. Задайте другой конкретный вопрос о StackOverflow, если у вас возникнут какие-либо трудности.

Надеюсь, это поможет.:)

Изменить:

Во-первых. Его единственный, кто должен закодировать в конце. Возьмите указатель и помощь отсюда. Не ожидайте полного кода. Подробнее см. Ниже.

A)

Вам нужно сделать ваши приложения в качестве контент-провайдера. Проверьте ссылку ниже. и следуйте приведенным в нем примерам. Вы попадете в приложение, чьи данные будут доступны другим. Его легко продвигаться вперед.

http://developer.android.com/guide/topics/providers/content-providers.html

http://developer.android.com/guide/topics/providers/content-provider-creating.html

http://www.vogella.com/articles/AndroidSQLite/article.html

Все ваши приложения для игр должны делать доступными данные для общего доступа. Теперь, как только вы закончите с этим.

Теперь вам просто нужно получить доступ ко всем данным в вашем приложении мониторинга и отобразить его.

B)

Как я сказал, если вы хотите сделать это через сервис. Вам не нужно запускать его навсегда. Просто запустите службу за один раз, загрузите данные из поставщика контента и сделайте все, что хотите. Установите будильник, чтобы вызвать службу на более позднем этапе, чтобы выполнить ту же операцию.

Я предполагаю, что каждые 10 минут ваш сервис может получать данные от поставщика контента (ваше игровое приложение, которое вы хотите контролировать). Это будет выглядеть следующим образом.

public class MonitoringService extends Service {

    private static final String ACTION_FETCH = "action_fetch";
    private static final int REPEAT_DATALOAD_INTERVAL_MS = 10 * 60 * 1000; // 10 Min

    private static PendingIntent makeSelfPendingIntent(Context context) {
        PendingIntent intent = PendingIntent.getService(
                           context, 0, makeSelfIntent(context), 0);
        return intent;
    }

    private static Intent makeSelfIntent(Context context) {
        Intent intent = new Intent(context, MonitoringService.class);
        intent.setAction(ACTION_FETCH);
        return intent;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent != null && intent.getAction() != null) {
            String action = intent.getAction();
            if (action.equals(ACTION_FETCH)) {
                loadDataFromContentProviderDoWhateverYouWantThen();
                setAlarmToRedoLoad();
                stopSelf();

            }
        }
        return Super.onStartCommand(intent, flags, startId);
    }

    private void setAlarmToRedoLoad() {
        AlarmManager alarmManager = (AlarmManager) 
                                     getSystemService(Context.ALARM_SERVICE);
        alarmManager.set(AlarmManager.RTC_WAKEUP,
                     System.currentTimeMillis() + REPEAT_DATALOAD_INTERVAL_MS,
                     makeSelfPendingIntent(getApplicationContext()));
    }

    private void loadDataFromContentProviderDoWhateverYouWantThen(){
        check this link how to load data from content provider.
        as your games app are content providers. It should be loaded easily.
        then do whatever you want display, upload anything

        http://developer.android.com/guide/topics/providers/content-provider-basics.html
    }

    // Call this method from onCreate of your monitoring app
    public static void start(Context context) {
        Intent intent = makeSelfIntent(context);
        context.startService(intent);
    }

}

C) убедитесь, что вы разрешили пользователю своего приложения мониторинга прекратить эту службу в любое время. Не забывайте отменять будильник в этом случае. поэтому он не бежит в фоновом режиме навсегда.

D) Вы также можете сделать вещание вещанием. Всякий раз, когда ваше приложение для игр сохраняет данные, оно должно транслировать это событие и позволить вашей службе вызываться после прослушивания этой трансляции. загружает его тогда только.

E) Как сказал Фил, вы также можете пойти и в Google Analytics.

Как вы хотите на самом деле сделать это сейчас зависит от вашего требования

Надеюсь, это поможет. Попробуйте и дайте мне знать, что еще проблема, с которой вы сталкиваетесь

Ответ 3

Используйте этот пример кода для проверки того, работает ли браузер

ActivityManager activityManager = (ActivityManager) this.getSystemService( ACTIVITY_SERVICE );
    List<RunningAppProcessInfo> procInfos = actvityManager.getRunningAppProcesses();
    for(int i = 0; i < procInfos.size(); i++){
        if(procInfos.get(i).processName.equals("com.android.browser")) {
            Toast.makeText(getApplicationContext(), "Browser is running", Toast.LENGTH_LONG).show();
        }
    }

Чтобы создать службу, вам необходимо создать класс, который простирается от базового класса Service и реализовать функцию в бесконечном цикле, например while(true) в функции onStartCommand(). не забудьте добавить свою службу в файл манифеста между тегами <service></service>, хороший учебник находится на http://www.vogella.com/articles/AndroidServices/article.html

Ответ 4

Следующие решения основаны на предположении, что у вас есть все пять приложений, которые вы пытаетесь контролировать. Если это будет разумно, я дам вам другое решение.

Теперь разрешите проблему.

  • Информационная служба о запуске приложения.
  • Проектирование сервиса
  • сохранение данных

Вы должны использовать Intent для трансляции сообщений о вашем приложении или нет. Используйте операции onPause и onResume для трансляции сообщений.

Теперь вызовите эту функцию из onPause и onResume,

   public void broadcastMessage(String method_name){
        Intent intent=new Intent("com.service.myservice");
        Bundle bundle=new Bundle();
        bundle.putString("app_name", this.getApplicationInfo().packageName);
        bundle.putLong("time", System.currentTimeMillis());
            //just to keep track if it is on pause or in on resume
        bundle.putString("method", method_name);
        intent.putExtras(bundle);
        this.startService(intent);
    }

и вы можете называть это следующим образом

public void onPause(){
        super.onPause();
        broadcastMessage(Thread.currentThread().getStackTrace()[1].getMethodName());
    }

или

public void onPause(){
        super.onPause();
        broadcastMessage("onPause");
    }

Итак, почему onPause и onResume, потому что геймер играет в игру только между этими вызовами функций.

Проектирование

Используйте IntentService и читайте этот, если хотите знать почему.

@Override
    protected void onHandleIntent(Intent intent) {
        // TODO Auto-generated method stub
        Bundle bundle=intent.getExtras();
        String app_name=bundle.getString("app_name");
        long time=bundle.getLong("time");
        //then do whatever you wanna do with this data

    }

Сохранение

ContentProvider - хороший вариант для хранения данных, а также очень мощный; хорошо интегрируются и масштабируются на Android, но в этом случае вам кажется, что вы хотите хранить только очень простую информацию, и поэтому вы также можете выбрать что-то очень простое.

В приведенном ниже тексте используется SharedPreferences.

 protected void storeData(String app_name, long time) {
        if ((app_name==null)||(time<=0)) return;
        SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(getApplicationContext());
        Editor editor = sharedPreferences.edit();
              //concatenating app name just to make key unique
        long start_time=sharedPreferences.getLong(app_name+"_start_time", -1);
        if (start_time==-1){
            //start time is empty means this is my start time
            // and user just started the app
            editor.putLong(app_name+"_start_time", time);
            editor.putLong(app_name+"_counter", sharedPreferences.getLong(app_name+"_counter", 0)+1);
        }else{
            //user is now closing the app. 
            long time_played=time-start_time;
            long total_time=sharedPreferences.getLong(app_name+"_total_time", 0);
            total_time=total_time+time_played;
            //saving total time
            editor.putLong(app_name+"_total_time", time);
            //removing start time for next pass
            editor.remove(app_name+"_start_time");
        }
        editor.commit();
    }
    public long getTotalTimeUserSpent(String app_name){
        SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(getApplicationContext());
        return sharedPreferences.getLong(app_name+"_total_time", 0);
    }
    public long numberOfTimeUserPlayed(String app_name){
        SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(getApplicationContext());
        return sharedPreferences.getLong(app_name+"_counter", 0);
    }

просто вызовите этот метод из onHandleIntent.

Последний кусок головоломки будет распространением. Вы можете распространять это в отдельном приложении или части этих пяти приложений. Отдельные приложения - это самый простой путь. Но если вы решите использовать это как часть всех пяти этих приложений, прочитайте эту дискуссию.

Ответ 5

Чтобы получить текущее запущенное приложение, вы можете использовать:

ActivityManager am = (ActivityManager) mContext.getSystemService(Activity.ACTIVITY_SERVICE);
String packageName = am.getRunningTasks(1).get(0).topActivity.getPackageName();

Вы можете периодически проверять это из вашей текущей службы. Вам также необходимо определить требуемое разрешение в вашем манифесте: android.permission.GET_TASKS

Ответ 6

Установка Covenant Eyes на вашем устройстве Android будет отслеживать весь интернет-трафик на предварительно установленном браузере телефона и сообщать о дополнительных приложениях, которые используются на устройства.