Эта статья представляет собой инструкцию по написанию и установке на целевое устройство приложения – device owner-а. Меня побудило написать эту статью то, что когда я сам принялся изучать этот вопрос, оказалось, что хорошей официальной документации с примерами нет, а информацию пришлось собирать с помощью Гугла.
В ОС Android, начиная с версии 5.0 Lollipop (API 21) появилась замечательная возможность управлять устройством программно, находясь в режиме device owner. Например, стало возможно производить «тихую» установку/удаление приложений, «скрывать» приложения (причем скрываются они качественно, т.е. исчезают из списка приложений в настройках, исчезают из лаунчера и списка последних использованных приложений), и делать многое другое. Это очень полезные возможности для реализации MDM. Обзор всех возможностей, которые предоставляются device owner-у выходят за рамки статьи, о них можно почитать здесь и здесь.
Терминология
Для начала определимся с терминами. Device owner – очевидно владелец устройства. Обычно владельцем устройства становится первый созданный пользователь при начальной настройке устройства, которая происходит после того как устройство впервые включается после покупки. Но в нашем случае мы сделаем так, что владельцем устройства будет наше приложение, которое и получит доступ к использованию расширенных возможностей управления устройством. Unprovisioned state – состояние устройства до того как будет проведена первичная настройка. Устройство находится в этом состоянии после первого включения и после wipe. Provisioned state – состояние устройства после того, как была проведена первичная настройка.
Что потребуется?
Для наших экспериментов нам понадобятся два устройства на которых есть NFC и одно из этих устройств нам придется wipe-нуть. Для того чтобы установить приложение – device owner на целевое устройство нужно загрузить его на сервер так, чтобы оно было доступно по URL целевому устройству, например http://example.com/deviceowner.apk
(я пробовал только протокол http). Далее нужно привести целевое устройство в unprovisioned state, например сделать wipe. После этого нужно установить на другое устройство приложение – инсталлятор. Потом нужно совместить эти два устройства так, чтобы был возможен обмен данными по NFC, обычно надо просто приложить устройства друг к другу задними поверхностями, далее надо подтвердить передачу по NFC тапнув по экрану устройства с приложением – инсталлятором. После этого первичная настройка целевого устройства продолжится и потребуется настроить сеть так, чтобы устройство смогло скачать apk файл с приложением – device owner-ом. После завершения первичной настройки приложение – device owner будет доступно для запуска и использования и его невозможно будет остановить/удалить никак и ничем, кроме wipe-а устройства.
Приложение – device owner
Я написал пример приложений device owner и инсталлятора. Ниже приведу наиболее интересные фрагменты кода. Весь код писать в статье смысла нет, в конце статьи будут ссылки на проекты с полным исходным кодом.
Приложение – device owner будет управлять видимостью других приложений. Т.е. с его помощью можно скрывать/показывать другие приложения. В приложенит есть класс AppsManager, он инкапсулирует построение списка и управление приложениями. Список получается в AsyncTaske-е:
private class LoadingTask extends AsyncTask<Void, Void, List<ApplicationInfo>> { @Override protected List<ApplicationInfo> doInBackground(final Void... params) { final PackageManager packageManager = mContext.getPackageManager(); return packageManager.getInstalledApplications( PackageManager.GET_META_DATA | PackageManager.GET_UNINSTALLED_PACKAGES ); } @Override protected void onPostExecute(final List<ApplicationInfo> result) { if (result != null) { mAppsList = result; } else { mAppsList = Lists.newArrayList(); } mStateObservable.setValue(State.IDLE); } }
Управление происходит через DevicePolicyManager:
mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); public void showApp(final ApplicationInfo app) { if (mStateObservable.getValue() != State.IDLE) { return; } mDevicePolicyManager.setApplicationHidden(mAdminComponent, app.packageName, false); } public void hideApp(final ApplicationInfo app) { if (mStateObservable.getValue() != State.IDLE) { return; } mDevicePolicyManager.setApplicationHidden(mAdminComponent, app.packageName, true); } public boolean isAppHidden(final ApplicationInfo app) { return mDevicePolicyManager.isApplicationHidden(mAdminComponent, app.packageName); }
Основой UI служит RecyclerView, все тривиально, приведу код адаптера:
private static class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> { private final Context mContext; private final LayoutInflater mInflater; private final PackageManager mPackageManager; private List<ApplicationInfo> mAppsList; private final AdministrationModeManager mAdministrationModeManager = AdministrationModeManager.getInstance(); private final AppsManager mAppsManager = AppsManager.getInstance(); public AppsListAdapter(final Context context) { mContext = context; mInflater = LayoutInflater.from(mContext); mPackageManager = mContext.getPackageManager(); } @Override public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { final View layout = mInflater.inflate(android.R.layout.simple_list_item_multiple_choice, parent, false); return new ViewHolder(layout); } @Override public void onBindViewHolder(final ViewHolder holder, final int position) { holder.mAppTitleTextView.setText(mAppsList.get(position).loadLabel(mPackageManager)); if (mAdministrationModeManager.isAdministrator() && mAdministrationModeManager.isDeviceOwner()) { holder.mAppTitleTextView.setChecked(!mAppsManager.isAppHidden(mAppsList.get(position))); } } @Override public int getItemCount() { return mAppsList == null ? 0 : mAppsList.size(); } public void setAppsList(final List<ApplicationInfo> appsList) { mAppsList = appsList; } public class ViewHolder extends RecyclerView.ViewHolder { public final CheckedTextView mAppTitleTextView; public ViewHolder(final View itemView) { super(itemView); mAppTitleTextView = (CheckedTextView) itemView.findViewById(android.R.id.text1); mAppTitleTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { if (mAdministrationModeManager.isAdministrator() && mAdministrationModeManager.isDeviceOwner()) { if (mAppTitleTextView.isChecked()) { mAppsManager.hideApp(mAppsList.get(getAdapterPosition())); } else { mAppsManager.showApp(mAppsList.get(getAdapterPosition())); } notifyDataSetChanged(); } } }); } } }
Особенность приложения в том, что ему нужно реализовать какой-нибудь reciever чтобы получить права администратора, хотя бы пустой:
public class AdminReceiver extends DeviceAdminReceiver { // do nothing }
В манифесте нужно указать этот receiver с соответствующими настройками:
<receiver android:name=".AdminReceiver" android:description="@string/app_name" android:label="@string/app_name" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" android:resource="@xml/device_owner_receiver"/> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> <action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/> </intent-filter> </receiver>
Для того, чтобы эти настройки заработали надо также положить в директорию ресурсов «xml» файл device_owner_receiver.xml с описанием того, чем приложение собирается управлять как администратор:
<?xml version="1.0" encoding="utf-8"?> <device-admin> <uses-policies> <limit-password/> <watch-login/> <reset-password/> <force-lock/> <wipe-data/> <expire-password/> <encrypted-storage/> <disable-camera/> </uses-policies> </device-admin>
В итоге приложение надо собрать и apk файл выложить на сервер.
Приложение – инсталлятор
Приложение – инсталлятор это просто приложение, которое запускается и после совмещения с целевым устройством передает по NFC данные в которых содержится информация откуда целевое устройство должно брать приложение – device owner, ниже код формирующий NFC-сообщение:
private class NdefMessageCallback implements NfcAdapter.CreateNdefMessageCallback { @Override public NdefMessage createNdefMessage(final NfcEvent event) { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final Properties properties = new Properties(); properties.put(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, "com.example.deviceowner"); // Make sure to put local time in the properties. This is necessary on some devices to // reliably download the device owner APK from an HTTPS connection. properties.put( DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME, String.valueOf(System.currentTimeMillis()) ); // To calculate checksum execute command (taken from http://stackoverflow.com/questions/26509770/checksum-error-while-provisioning-android-lollipop): // cat Something.apk | openssl dgst -binary -sha1 | openssl base64 | tr '+/' '-_' | tr -d '=' properties.put( DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM, "[Device owner app checksum]" ); properties.put( DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION, "[Device owner app URL]" ); try { properties.store(outputStream, getString(R.string.nfc_comment)); final NdefRecord record = NdefRecord.createMime( DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC, outputStream.toByteArray() ); return new NdefMessage(new NdefRecord[]{record}); } catch (final IOException e) { throw new RuntimeException(e); } } }
Обратите внимание на следующие моменты:
- Не забудьте добавить
<uses-permission android:name="android.permission.NFC" />
в манифест; - Вам нужно указать package name вашего приложения – device owner-a (в нашем случае это «com.example.deviceowner»)
- Вам нужно указать правильный URL по которому доступен apk-файл приложения – device owner-a (вместо [Device owner app URL])
- А также вам нужно указать контрольную сумму (вместо [Device owner app checksum]). Контрольная сумма считается коммандой
cat Something.apk | openssl dgst -binary -sha1 | openssl base64 | tr '+/' '-_' | tr -d '='
(команда взята отсюда)
Эксперименты
Оба приложения надо собрать, инсталлятор установить на одно устройство, device owner надо выложить на сервер, целевое устройство wipe-нуть. После этого можно начать эксперименты.
Для демонстрации работы функции скрытия приложения я скрою системные настройки на целевом устройстве:
Здесь мы видим, что настройки есть в списке приложений.
Здесь мы видим, что настройки есть в списке последних использованных приложений.
Выключаем приложение – настройки.
Настройки исчезли из списка последних использованных приложений.
И из списка приложений.
Даже если вы попытаетесь запустить настройки из “шторки” (обведено зеленым прямоугольником) и другими способами у вас ничего не получится.
Ссылки на проекты
Исходные коды проектов приложений:
github.com/raynor73/DeviceOwner
github.com/raynor73/NfcProvisioning
ссылка на оригинал статьи http://habrahabr.ru/post/271951/
Добавить комментарий