Виджеты – это небольшие «мини‑приложения»‚ которые живут на домашнем экране Android‑устройства и позволяют выполнять часто используемые действия без запуска полного приложения. В этой статье мы разберём‚ как создать и настроить виджет‚ который будет управлять функциями телефона: звонками‚ музыкой‚ яркостью‚ фонариком и даже голосовыми командами.
Содержание
- Что такое виджет управления телефоном?
- Популярные типы телефонных виджетов
- Архитектура Android‑виджета (RemoteViews‚ AppWidgetProvider)
- Необходимые разрешения
- Пошаговое создание собственного виджета
- Готовые примеры кода
- Лучшие практики и рекомендации
- Отладка и типичные ошибки
- FAQ
Что такое виджет управления телефоном?
Виджет – это RemoteViews‚ который отображается в отдельном процессе (обычно в процессе лаунчера). Он может реагировать на PendingIntent‚ посылая Broadcast‑ы в ваш AppWidgetProvider. С помощью этого механизма можно управлять системными функциями телефона‚ не открывая приложение.
Популярные типы телефонных виджетов
- Медиа‑контроллер – пауза/воспроизведение‚ переключение треков‚ регулировка громкости.
- Виджет звонка/быстрого набора – один клик – звонок на часто используемый номер.
- Фонарик – включить/выключить LED‑фонарик.
- Регулятор яркости и режима «Не беспокоить».
- Голосовое управление – запуск ассистента‚ переключение приложений по мимическим жестам.
Архитектура Android‑виджета
Для создания виджета необходимо понять три ключевых компонента:
- XML‑разметка виджета (
res/layout/widget_layout.xml) – описывает внешний вид. - AppWidgetProvider – подкласс
BroadcastReceiver‚ получающий события (обновление‚ клик). - RemoteViews – объект‚ позволяющий менять UI из вашего процесса.
Ниже схематично показана связь компонентов:
+-------------------+ +-------------------+ | Launcher (Home) | <---> | RemoteViews | | (процесс UI) | | (в вашем прилож.)| +-------------------+ +-------------------+ ^ | | Broadcast (Intent) | v v +-------------------+ +-------------------+ | AppWidgetProvider | <---> | Service/Activity | +-------------------+ +-------------------+
Необходимые разрешения
Для управления телефоном требуется добавить в AndroidManifest.xml соответствующие uses-permission. Ниже пример минимального набора:
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
Начиная с Android 6.0 (API 23) большинство из этих прав запрашиваются в runtime. Виджет может попросить пользователя открыть настройки‚ где он вручную включит нужные разрешения.
Пошаговое создание собственного виджета
Шаг 1. Создаём XML‑разметку
<!-- res/layout/widget_phone_control.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="@drawable/widget_bg"
android:padding="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/btn_call"
android:src="@drawable/ic_phone"
android:contentDescription="@string/call_desc"
android:layout_width="48dp"
android:layout_height="48dp"/>
<ImageButton
android:id="@+id/btn_flash"
android:src="@drawable/ic_flashlight"
android:contentDescription="@string/flash_desc"
android:layout_width="48dp"
android:layout_height="48dp"/>
<ImageButton
android:id="@+id/btn_music"
android:src="@drawable/ic_music"
android:contentDescription="@string/music_desc"
android:layout_width="48dp"
android:layout_height="48dp"/>
</LinearLayout>
Шаг 2. Описываем виджет в манифесте
<receiver android:name=".PhoneControlWidgetProvider"
android:label="@string/widget_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/phone_control_widget_info" />
</receiver>
Файл res/xml/phone_control_widget_info.xml задаёт размеры и обновление:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="110dp"
android:minHeight="110dp"
android:updatePeriodMillis="0"
android:initialLayout="@layout/widget_phone_control"
android:previewImage="@drawable/widget_preview"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen" />
Шаг 3. Реализуем AppWidgetProvider
public class PhoneControlWidgetProvider extends AppWidgetProvider {
private static final String ACTION_CALL = "com.example.ACTION_CALL";
private static final String ACTION_FLASH = "com.example.ACTION_FLASH";
private static final String ACTION_MUSIC = "com.example.ACTION_MUSIC";
@Override
public void onUpdate(Context context‚ AppWidgetManager appWidgetManager‚ int[] appWidgetIds) {
for (int widgetId : appWidgetIds) {
RemoteViews views = new RemoteViews(context.getPackageName‚
R.layout.widget_phone_control);
// Кнопка «Позвонить» – открывает диалог с номером из ресурсов
Intent callIntent = new Intent(context‚ PhoneControlWidgetProvider.class);
callIntent.setAction(ACTION_CALL);
PendingIntent callPi = PendingIntent.getBroadcast(context‚ 0‚ callIntent‚
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.btn_call‚ callPi);
// Кнопка «Фонарик»
Intent flashIntent = new Intent(context‚ PhoneControlWidgetProvider.class);
flashIntent.setAction(ACTION_FLASH);
PendingIntent flashPi = PendingIntent.getBroadcast(context‚ 1‚ flashIntent‚
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.btn_flash‚ flashPi);
// Кнопка «Музыка»
Intent musicIntent = new Intent(context‚ PhoneControlWidgetProvider.class);
musicIntent.setAction(ACTION_MUSIC);
PendingIntent musicPi = PendingIntent.getBroadcast(context‚ 2‚ musicIntent‚
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.btn_music‚ musicPi);
appWidgetManager.updateAppWidget(widgetId‚ views);
}
}
@Override
public void onReceive(Context context‚ Intent intent) {
super.onReceive(context‚ intent);
String action = intent.getAction;
if (ACTION_CALL.equals(action)) {
makeCall(context);
} else if (ACTION_FLASH.equals(action)) {
toggleFlashlight(context);
} else if (ACTION_MUSIC.equals(action)) {
controlMusic(context);
}
}
private void makeCall(Context ctx) {
// Номер берём из ресурсов‚ но в реальном проекте можно хранить в SharedPreferences
String number = ctx.getString(R.string.default_phone_number); // +7 (495) --*
Intent call = new Intent(Intent.ACTION_CALL);
call.setData(Uri.parse("tel:" + number));
if (ActivityCompat.checkSelfPermission(ctx‚ Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
ctx.startActivity(call.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else {
// Попросить разрешение у пользователя
Toast.makeText(ctx‚ "Разрешите звонки в настройках"‚ Toast.LENGTH_SHORT).show;
}
}
private void toggleFlashlight(Context ctx) {
CameraManager camManager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
try {
String cameraId = camManager.getCameraIdList[0];
boolean isOn = ctx.getSharedPreferences("widget"‚ Context.MODE_PRIVATE)
.getBoolean("flash_on"‚ false);
camManager.setTorchMode(cameraId‚ !isOn);
ctx.getSharedPreferences("widget"‚ Context.MODE_PRIVATE)
.edit.putBoolean("flash_on"‚ !isOn).apply;
} catch (Exception e) {
e.printStackTrace;
}
}
private void controlMusic(Context ctx) {
// Пример: переключить воспроизведение/пауза через MediaSession
Intent i = new Intent("android.media.action.DISPLAY_AUDIO_EFFECT_CONTROL_PANEL");
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(i);
}
}
Шаг 4. Добавляем ресурсы
- Иконки
ic_phone.png‚ic_flashlight.png‚ic_music.pngвres/drawable. - Строка
<string name="default_phone_number">+7 (495) --*</string>.
Шаг 5. Тестируем на реальном устройстве
- Установите приложение через
adb installили Android Studio. - Перейдите на главный экран‚ откройте меню «Виджеты»‚ найдите «Управление телефоном» и перетащите его.
- Нажмите кнопку «Позвонить» – появится запрос разрешения‚ после чего будет выполнен звонок на скрытый номер.
- Кнопка «Фонарик» включит/выключит LED‑фонарик‚ а «Музыка» откроет панель управления аудио.
Готовые примеры кода
Виджет «Быстрый набор» (10 контактов)
Список контактов хранится в SharedPreferences‚ а каждый номер частично скрыт‚ чтобы не раскрывать личные данные.
// Пример сохранения контакта
SharedPreferences sp = context.getSharedPreferences("quick_dial"‚ MODE_PRIVATE);
sp;edit.putString("contact_1_name"‚ "Иванов И.И.") // Фамилия и инициалы
.putString("contact_1_number"‚ "+7 (495) --*") // маска
.apply;
В разметке виджета выводятся только имена‚ а при нажатии – открывается диалог подтверждения звонка.
Виджет «Регулятор громкости»
private void setVolume(Context ctx‚ int direction) {
AudioManager am = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
int cur = am.getStreamVolume(AudioManager.STREAM_MUSIC);
int max = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int newVol = Math.min(max‚ Math.max(0‚ cur + direction));
am.setStreamVolume(AudioManager.STREAM_MUSIC‚ newVol‚ 0);
}
Виджет «Always‑On Display» для Android 14 (Samsung‚ Казахстан)
В Android 14 можно разместить небольшие иконки‑виджеты под часами на экране блокировки. Для этого в манифесте указывается android:widgetCategory="keyguard". Пример кода:
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="48dp"
android:minHeight="48dp"
android:updatePeriodMillis="0"
android:initialLayout="@layout/widget_aod"
android:widgetCategory="keyguard"
android:previewImage="@drawable/aod_preview" />
Лучшие практики и рекомендации
- Минимальный размер UI – виджеты должны быть читаемы даже на небольших экранах (не менее 48 dp для кнопок).
- Не блокировать UI потоком – любые длительные операции (например‚ запрос к сети) вынесите в
ServiceилиWorkManager. - Обновление только при необходимости – задайте
updatePeriodMillis="0"и вызывайтеAppWidgetManager.updateAppWidgetвручную. - Обрабатывайте отсутствие разрешений – показывайте
Toastили открывайтеIntent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS). - Тестируйте на разных лаунчерах – некоторые кастомные лаунчеры (например‚ Nova Launcher) могут ограничивать размер виджета.
- Поддержка темной темы – используйте атрибуты
?attr/colorPrimaryвместо жёстко заданных цветов.
Отладка и типичные ошибки
| Ошибка | Причина | Решение |
|---|---|---|
| Виджет не реагирует на клики | Отсутствует PendingIntent или использованы неверные флаги | Убедитесь‚ что PendingIntent.FLAG_IMMUTABLE (для Android 12+) установлен. |
Кнопка «Фонарик» бросает SecurityException | Нет разрешения android.permission.FLASHLIGHT или устройство не поддерживает фонарик | Запросите разрешение в runtime и проверяйте hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH). |
| Виджет «Быстрый набор» открывает пустой диалог | Контакты не сохранены в SharedPreferences | Проверьте‚ что apply вызывается после putString. |
FAQ
- Можно ли разместить несколько одинаковых виджетов?
- Да. Каждый экземпляр получает свой
appWidgetId‚ который можно использовать для хранения индивидуальных настроек. - Работают ли виджеты в режиме «экономия батареи»?
- Да‚ но Android может ограничить частоту обновлений. Поэтому лучше полагаться на пользовательские действия‚ а не на таймеры.
- Можно ли добавить в виджет карту или WebView?
- Нет.
RemoteViewsподдерживает лишь ограниченный набор виджетов (TextView‚ ImageView‚ Button‚ etc.). Для карт используйтеPendingIntent→ открытие отдельного Activity. - Как добавить поддержку локализации?
- Создайте файлы
res/values/strings.xmlиres/values-ru/strings.xml‚ а в разметке используйте ссылки@string/….
Контактные данные разработчиков (частично скрыты)
Если у вас возникли вопросы по реализации‚ вы можете связаться с авторами проекта:
- Иванов И.И.‚ ivanov@example.com‚ тел.: +7 (495) —
- Петрова А.С.‚ г. Москва‚ ул. Тверская‚ д. 12‚ оф.
- Служба поддержки: —*»>+7 (800) —*
Мы надеемся‚ что данное руководство поможет вам создать удобный и надёжный виджет управления телефоном. Удачной разработки!