# Art Keyboard — Google Play Malware Policy Remediation Brief

**Document for partner / development team handoff**

| Field | Value |
|---|---|
| App name | Keyboard Themes - Fonts, Emoji ("Art Keyboard") |
| Package ID | `com.artkeyboard.aikeyboard.fonts.keyboard` |
| Developer account | AppLock Inc. (`myappszone222@gmail.com`) |
| Play Console status | **Suspended** (Malware policy) |
| Flagged version code | **21** (per Google notice dated 28/06/2026) |
| Analyzed artifact | XAPK v23.0 (versionCode 23), 80.3 MB, from APKPure |
| Analysis date | 29/06/2026 |
| Internal namespace in code | `org.keyboard.photokeyboard.inputmethod.*` (originally a "Photo Keyboard" base project) |

---

## 1. Executive summary

Google Play suspended Art Keyboard under the **Malware** policy. After decompiling the published APK we confirmed the suspension is **technically justified**: the app declares and contains code for many capabilities that have no relation to an Input Method Editor (IME) — including an Accessibility Service with maximum privileges, a Notification Listener, MediaProjection-based screen capture/recording, a phone dialer replacement (InCallService + CallScreeningService), and a contacts/app-lock framework copied from a separate "AppLock"-style codebase (`com.goodwy.commons`).

The combination of these capabilities (AccessibilityService + NotificationListener + MediaProjection + RECORD_AUDIO + READ_CONTACTS + SYSTEM_ALERT_WINDOW) matches the static signature Google Play uses to identify **stalkerware / spyware**, regardless of developer intent. To recover the app on Play Store, every component listed in Section 5 must be **fully removed from the APK** (not just disabled at runtime or hidden behind remote config) and the permission list trimmed to the IME-essential set in Section 7.

---

## 2. Google Play notice — what was reported

From Google Play Console email (28/06/2026, 07:52):

> "Your app is not compliant with the **Malware** policy. We don't allow apps with any code that could put a user, a user's data, or a device at risk."
>
> "Issue details — We found an issue in the following area(s): Version code 21"
>
> "Further policy violations may lead to your Google Play Developer account and any other related accounts being terminated."

---

## 3. Methodology

1. Extracted the XAPK bundle (`base.apk` + locale/abi/dpi config splits).
2. Decompiled `base.apk` with **jadx** to recover Java pseudo-code and the merged `AndroidManifest.xml`.
3. Static scan of:
   - Manifest permissions, services, receivers, providers, queries.
   - XML resources (`res/xml/accessibilityservice.xml`, etc.).
   - Java source folders (~20,103 classes) for sensitive APIs: `MediaProjection`, `AccessibilityService`, `NotificationListenerService`, `InCallService`, `CallScreeningService`, `MediaProjectionManager`, `SmsManager`, `ContactsContract`, dynamic loaders.
4. Cross-checked findings against Google Play **Malware policy**, **Permissions and APIs that Access Sensitive Information**, and **Default Handlers** guidance.

This is a **black-box static analysis** of the published APK. We did not have source access; the findings are based on declarations and decompiled code only. They reflect what Google Play's review pipeline can also see.

---

## 4. Root cause hypothesis — base project inherited from AppLock

The developer namespace inside the APK is **`org.keyboard.photokeyboard.inputmethod.*`** (a generic "Photo Keyboard" base), but it is bundled together with classes from **`com.goodwy.commons.*`** (a known Android utility framework used in dialer/contacts/SMS/voice-recorder applications) and a full self-contained iPhone-style "Control Center" module under **`org.keyboard.photokeyboard.inputmethod.ui.controlcenter.*`**.

The most likely explanation — consistent with the developer account being `AppLock Inc.` — is that the team **forked an existing base project** (likely their AppLock / Control Center / Dialer template) and **added IME functionality on top**, without stripping the original modules. As a result, the Keyboard APK ships with:

- AppLock features (system locker, AccessibilityService)
- Control-Center features (screen capture, media control, overlay)
- Dialer / Caller features (InCallService, CallScreeningService, contacts)
- Voice recorder hooks (RECORD_AUDIO + FOREGROUND_SERVICE_MICROPHONE)
- Live wallpapers × 4
- The actual IME

Google's reviewer (human or automated) sees one app with all of the above and concludes "this is a surveillance-capable keyboard" — a strong Malware policy trigger.

---

## 5. Findings — components that MUST be removed

Each row identifies a real, declared component in the APK with its evidence and the policy concern.

### 5.1 AccessibilityService with maximum privileges — CRITICAL

**Evidence — `res/xml/accessibilityservice.xml`:**

```xml
<accessibility-service
    android:accessibilityEventTypes="typeAllMask"
    android:packageNames="com.controlcenter.tool.iphone"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:notificationTimeout="5"
    android:accessibilityFlags="flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
    android:canRetrieveWindowContent="true"/>
```

**Implementation:** `org.keyboard.photokeyboard.inputmethod.ui.controlcenter.services.ServiceControl extends android.accessibilityservice.AccessibilityService` (551 lines).

**Why this triggers Malware policy:**
- `typeAllMask` + `canRetrieveWindowContent=true` + `flagRetrieveInteractiveWindows|flagIncludeNotImportantViews` grants the app the ability to **read the content of every other app's UI** — including banking apps, password managers, messaging apps, OTP screens.
- The `packageNames` attribute targets `com.controlcenter.tool.iphone` — **a different package, not this app**. This is either leftover copy-paste from another project or deliberate automation of a third-party app. Either reading is suspicious to a reviewer.
- An IME has **no legitimate use** for AccessibilityService. Google Play's `IsAccessibilityTool` rule restricts AccessibilityService to apps whose primary function is accessibility.

**Action:** Delete `ServiceControl.java`, delete `res/xml/accessibilityservice.xml`, remove the `<service>` block from `AndroidManifest.xml`, drop the `BIND_ACCESSIBILITY_SERVICE` permission.

---

### 5.2 MediaProjection screen capture / screen recorder — CRITICAL

**Evidence — `ServiceScreen.java`:**

```java
public class ServiceScreen extends android.app.Service {
    private MediaProjection mediaProjection;
    private MediaProjectionManager mediaProjectionManager;
    // ...
    mediaProjectionManager.getMediaProjection(-1, (Intent) f59059n.clone());
    // ...
    startForeground(12222, notificationBuild, 32); // FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
}
```

Supporting components:
- `org.keyboard.photokeyboard.inputmethod.ui.controlcenter.ui.ScreenshotActivity`
- `org.keyboard.photokeyboard.inputmethod.ui.controlcenter.ui.RecordSettingActivity`
- Third-party library `com.hbisoft.hbrecorder.*` (HBRecorder — screen-recording library)
- System overlay window using `WindowManager.LayoutParams.type = 2038` (TYPE_APPLICATION_OVERLAY)

**Why this triggers Malware policy:** An IME has no use case that requires capturing the entire device screen. Combined with the Accessibility Service this is the classic stalkerware pattern.

**Action:** Delete the entire `controlcenter` package, delete `ScreenRecordService` and HBRecorder dependency, drop permissions `FOREGROUND_SERVICE_MEDIA_PROJECTION`, `MEDIA_CONTENT_CONTROL`, `CAPTURE_VIDEO_OUTPUT`, `SYSTEM_ALERT_WINDOW`, `ACTION_MANAGE_OVERLAY_PERMISSION`.

---

### 5.3 NotificationListenerService — CRITICAL

**Evidence — `AndroidManifest.xml`:**

```xml
<service
    android:name=".ui.controlcenter.services.NotificationService"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService"/>
    </intent-filter>
</service>
```

`NotificationService.java` is currently a 5-line empty subclass:

```java
public class NotificationService extends android.service.notification.NotificationListenerService { }
```

**Why this triggers Malware policy:**
- Even though the implementation is empty, the declaration is enough to **request the user grant Notification Access**, after which logic can be added in a later update.
- Google Play treats `NotificationListenerService` as a high-risk permission for non-launcher/non-assistant apps, because it exposes the contents of every notification (OTP codes, banking alerts, private messages).

**Action:** Delete the `<service>` declaration entirely. Delete `NotificationService.java`.

---

### 5.4 Phone Dialer replacement (InCallService) — CRITICAL

**Evidence — `AndroidManifest.xml`:**

```xml
<service android:name=".ui.caller.service.CallService"
         android:permission="android.permission.BIND_INCALL_SERVICE">
    <intent-filter><action android:name="android.telecom.InCallService"/></intent-filter>
</service>

<service android:name=".ui.caller.service.SimpleCallScreeningService"
         android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter><action android:name="android.telecom.CallScreeningService"/></intent-filter>
</service>
```

`CallService.java` overrides `onCallAdded` and opens a full-screen `CallActivity` on every incoming call. There is a full caller-id UI package at `org.keyboard.photokeyboard.inputmethod.ui.caller.*`.

**Why this triggers Malware policy:**
- A keyboard advertising itself as "Keyboard / Fonts / Emoji" should not register as the system dialer or call-screening service.
- Possession of dialer permissions (`CALL_PHONE`, `ANSWER_PHONE_CALLS`, `MANAGE_OWN_CALLS`, `READ_PHONE_STATE`) is reserved for apps in the dialer category.

**Action:** Delete the entire `ui.caller.*` package (services, activities, helpers, receivers). Drop the four phone-related permissions and `BIND_INCALL_SERVICE` / `BIND_SCREENING_SERVICE` service declarations.

---

### 5.5 Goodwy commons (AppLock / blocked numbers / contacts framework) — HIGH

**Evidence — directory tree:**

```
com/goodwy/commons/
  ├─ activities/AppLockActivity.java
  ├─ activities/ManageBlockedNumbersActivity.java
  ├─ activities/ContributorsActivity.java
  ├─ activities/FAQActivity.java
  ├─ activities/LicenseActivity.java
  ├─ receivers/RightBroadcastReceiver.java
  ├─ models/contacts/...
  ├─ databases/...
  ├─ dialogs/...
  ├─ helpers/...
  └─ views/...
```

**Manifest also includes a `<queries>` block listing other Goodwy apps:**

```xml
<queries>
    <package android:name="com.goodwy.audiobook"/>
    <package android:name="com.goodwy.calendar"/>
    <package android:name="com.goodwy.contacts"/>
    <package android:name="com.goodwy.dialer"/>
    <package android:name="com.goodwy.files"/>
    <package android:name="com.goodwy.keyboard"/>
    <package android:name="com.goodwy.smsmessenger"/>
    <package android:name="com.goodwy.voicerecorder"/>
    <package android:name="com.goodwy.voicerecorderfree"/>
</queries>
```

**Why this triggers Malware policy / Deceptive Behavior:**
- These classes are unrelated to keyboard functionality. They are utility code originally written for AppLock-style and dialer apps.
- The `<queries>` block makes the keyboard scan whether the user has any of nine other apps installed — behavior typical of cross-app surveillance / ecosystem-linking apps.

**Action:** Delete the entire `com/goodwy/` directory from the source tree and remove the `<queries>` block from the manifest.

---

### 5.6 Microphone / Camera / GPS / Bluetooth — HIGH

These permissions are declared with no corresponding legitimate IME feature visible in the code:

| Permission | Declared because of | Action |
|---|---|---|
| `RECORD_AUDIO` + `FOREGROUND_SERVICE_MICROPHONE` | Inherited from voice-recorder / caller stack | Remove unless app implements an explicit, user-initiated **voice-to-text** feature with on-screen UI |
| `CAMERA` + `CAPTURE_VIDEO_OUTPUT` | No clear use case | Remove |
| `ACCESS_FINE_LOCATION` + `ACCESS_COARSE_LOCATION` | No clear use case | Remove |
| `BLUETOOTH`, `BLUETOOTH_ADMIN`, `BLUETOOTH_CONNECT` | No pairing/scanning code | Remove |
| `USE_FINGERPRINT` | Inherited from AppLock | Remove |
| `WRITE_SETTINGS` | No system-settings modification needed for IME | Remove |
| `READ_SYNC_SETTINGS`, `WRITE_SYNC_SETTINGS` | No sync feature | Remove |
| `READ_CONTACTS` | Inherited from dialer | Remove |
| `INSTALL_SHORTCUT` | Optional, low value | Remove |

---

### 5.7 Live wallpaper services — MEDIUM (review with product)

**Evidence:** Four `<service>` declarations with action `android.service.wallpaper.WallpaperService`, anchored on `org.keyboard.photokeyboard.inputmethod.ui.wallpaper.service.GLWallpaperService`. Requires `SET_WALLPAPER` and `<uses-feature android:name="android.software.live_wallpaper"/>`.

**Why this is borderline:** Live wallpapers are a legitimate, well-known Android feature and not inherently policy-violating. **However**, in a "keyboard" app they expand the surface area of justification needed during review. If live wallpapers are not a meaningful driver of MAU/revenue, removing them simplifies the resubmit story.

**Action:** Confirm with product whether to keep. If kept, ensure clear in-app entry point, screenshots on Play listing, and Data Safety justification.

---

## 6. Component-by-component remediation matrix

| Status | Components (Activities / Services / Resources / Permissions) |
|---|---|
| **🟢 KEEP** (core IME) | `LatinIME`, `AndroidSpellCheckerService`, `DictionaryProvider`, all keyboard layout XMLs, `SettingsActivity`, `AppLanguageActivity`, `KeyboardLanguageActivity`, `AppThemeActivity`, `ThemePreviewActivity`, `FontsActivity`, `EffectsActivity`, `SoundActivity`, `KaomojiActivity`, `KaomojiPreviewActivity`, `StickerPreviewActivity`, `FavouriteActivity`, `CollectionActivity`, `SearchActivity`, `CustomDiyActivity`, `CropActivity`, `GalleryActivity` (for DIY theme picker), `DashboardActivity`, `SetupActivity`, `SplashNewActivity`, `NativeFullSplashActivity`, `PremiumKeyboardActivity`, `FeedbackActivity`, `PolicyActivity`, `WebViewActivity`, `OpenActivity`, `PermissionsActivity`, `DictionaryPackInstallBroadcastReceiver`, `SystemBroadcastReceiver`, Apero/ad SDK integration |
| **🟡 PRODUCT DECISION** | Live wallpaper services (`GLWallpaperService` × 4) — keep only if business-critical. Voice typing — keep RECORD_AUDIO only if there is a user-visible voice-input button. |
| **🔴 DELETE** | `controlcenter.services.ServiceControl`, `controlcenter.services.ServiceScreen`, `controlcenter.services.NotificationService`, `controlcenter.ui.ScreenshotActivity`, `controlcenter.ui.RecordSettingActivity`, all of `ui.controlcenter.*`, `ui.caller.service.CallService`, `ui.caller.service.SimpleCallScreeningService`, `ui.caller.activity.CallActivity`, `ui.caller.receviers.CallActionReceiver`, all of `ui.caller.*`, all of `com.goodwy.commons.*`, third-party lib `com.hbisoft.hbrecorder.*`, `res/xml/accessibilityservice.xml` |

---

## 7. Target permission set after remediation

The following is the **minimum and recommended** `uses-permission` set for an IME with themes, fonts, emoji, stickers, IAP, ads, and FCM.

```xml
<!-- IME core -->
<uses-permission android:name="android.permission.VIBRATE"/>

<!-- Network: theme/font/sticker downloads, ads, FCM -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<!-- Notifications (Android 13+) for theme/promo updates -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<!-- DIY theme: user-picked image from gallery (Android 13+ scoped) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>

<!-- Wake lock for foreground keyboard input handling -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>

<!-- Billing & licensing -->
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="com.android.vending.CHECK_LICENSE"/>

<!-- FCM (push) and Install Referrer -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE"/>

<!-- Ad SDKs (AdMob / GMA) — these are standard -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID"/>
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION"/>
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS"/>
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE"/>

<!-- OPTIONAL — only if live wallpaper feature is retained -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<!-- OPTIONAL — only if real voice typing UI exists -->
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO"/>          -->
<!-- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/> -->
```

**Permissions REMOVED relative to v23 (28 items)**:

```
RECEIVE_BOOT_COMPLETED (duplicated declaration) ×1
ACCESS_WIFI_STATE, CHANGE_WIFI_STATE, CHANGE_NETWORK_STATE
READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE (replaced by READ_MEDIA_IMAGES on SDK 33+)
CAMERA, CAPTURE_VIDEO_OUTPUT
SCHEDULE_EXACT_ALARM, USE_EXACT_ALARM, USE_FULL_SCREEN_INTENT
BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_CONNECT
WRITE_SETTINGS, BIND_ACCESSIBILITY_SERVICE, ACCESS_NOTIFICATION_POLICY
FOREGROUND_SERVICE_MEDIA_PROJECTION, MEDIA_CONTENT_CONTROL
RECORD_AUDIO, FOREGROUND_SERVICE_MICROPHONE (unless voice typing retained)
ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION
SYSTEM_ALERT_WINDOW, ACTION_MANAGE_OVERLAY_PERMISSION
FLASHLIGHT
READ_SYNC_SETTINGS, WRITE_SYNC_SETTINGS
CALL_PHONE, READ_CONTACTS, MANAGE_OWN_CALLS, ANSWER_PHONE_CALLS, READ_PHONE_STATE
INSTALL_SHORTCUT, com.goodwy.android.permission.WRITE_GLOBAL_SETTINGS
USE_FINGERPRINT
com.amazon.privacypass.ATTEST
com.samsung.android.mapsagent.permission.READ_APP_INFO
com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA
```

---

## 8. Service / receiver declarations to delete from manifest

```xml
<!-- DELETE -->
<service android:name=".ui.controlcenter.services.ServiceControl">
    <intent-filter><action android:name="android.accessibilityservice.AccessibilityService"/></intent-filter>
    <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice"/>
</service>

<service android:name=".ui.controlcenter.services.ServiceScreen" .../>

<service android:name=".ui.controlcenter.services.NotificationService"
         android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter><action android:name="android.service.notification.NotificationListenerService"/></intent-filter>
</service>

<service android:name=".ui.caller.service.CallService"
         android:permission="android.permission.BIND_INCALL_SERVICE">
    <intent-filter><action android:name="android.telecom.InCallService"/></intent-filter>
</service>

<service android:name=".ui.caller.service.SimpleCallScreeningService"
         android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter><action android:name="android.telecom.CallScreeningService"/></intent-filter>
</service>

<receiver android:name=".ui.caller.receviers.CallActionReceiver" .../>

<activity android:name="com.goodwy.commons.activities.AppLockActivity"/>
<activity android:name="com.goodwy.commons.activities.ManageBlockedNumbersActivity"/>
<activity android:name="com.goodwy.commons.activities.ContributorsActivity"/>
<activity android:name="com.goodwy.commons.activities.FAQActivity"/>
<activity android:name="com.goodwy.commons.activities.LicenseActivity"/>
<receiver android:name="com.goodwy.commons.receivers.RightBroadcastReceiver"/>

<!-- DELETE the entire <queries> block listing com.goodwy.* packages -->
```

**XML resource to delete:** `res/xml/accessibilityservice.xml`.

**Source folders to delete:**
- `org/keyboard/photokeyboard/inputmethod/ui/controlcenter/` (entire tree)
- `org/keyboard/photokeyboard/inputmethod/ui/caller/` (entire tree)
- `com/goodwy/` (entire tree)
- `com/hbisoft/hbrecorder/` (entire tree)

---
