Эта статья представляет собой инструкцию по написанию и установке на целевое устройство приложения – 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 целевому устройству, например
Я написал пример приложений device owner и инсталлятора. Ниже приведу наиболее интересные фрагменты кода. Весь код писать в статье смысла нет, в конце статьи будут ссылки на проекты с полным исходным кодом.
Приложение – device owner будет управлять видимостью других приложений. Т.е. с его помощью можно скрывать/показывать другие приложения. В приложенит есть класс AppsManager, он инкапсулирует построение списка и управление приложениями. Список получается в AsyncTaske-е:
Управление происходит через DevicePolicyManager:
Основой UI служит RecyclerView, все тривиально, приведу код адаптера:
Особенность приложения в том, что ему нужно реализовать какой-нибудь reciever чтобы получить права администратора, хотя бы пустой:
В манифесте нужно указать этот receiver с соответствующими настройками:
Для того, чтобы эти настройки заработали надо также положить в директорию ресурсов «xml» файл device_owner_receiver.xml с описанием того, чем приложение собирается управлять как администратор:
В итоге приложение надо собрать и apk файл выложить на сервер.
Приложение – инсталлятор это просто приложение, которое запускается и после совмещения с целевым устройством передает по NFC данные в которых содержится информация откуда целевое устройство должно брать приложение – device owner, ниже код формирующий NFC-сообщение:
Обратите внимание на следующие моменты:
Оба приложения надо собрать, инсталлятор установить на одно устройство, device owner надо выложить на сервер, целевое устройство wipe-нуть. После этого можно начать эксперименты.
Для демонстрации работы функции скрытия приложения я скрою системные настройки на целевом устройстве:

Здесь мы видим, что настройки есть в списке приложений.

Здесь мы видим, что настройки есть в списке последних использованных приложений.

Выключаем приложение – настройки.

Настройки исчезли из списка последних использованных приложений.

И из списка приложений.

Даже если вы попытаетесь запустить настройки из “шторки” (обведено зеленым прямоугольником) и другими способами у вас ничего не получится.
Исходные коды проектов приложений:
github.com/raynor73/DeviceOwner
github.com/raynor73/NfcProvisioning
В ОС 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
Комментарии (3)
stoplinux
02.12.2015 19:24Гениально! А я то думал, почему Sony после вайпа пишет Установка обновлений по NFC.
glazik
Спасибо.
А есть ли возможность поставить девайс-оунер приложение без предварительного вайпа?
Raynor73
Нет, нельзя. Device owner ставится только на unprovisioned-устройство. Т.е. до того как оно будет настроено в первый раз после покупки, или сразу после wipe-а.