Реализация приложения – device owner-а под Android

от автора

Эта статья представляет собой инструкцию по написанию и установке на целевое устройство приложения – 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *