| Update Impact on Dependencies |
Updates
Configuring Auto-Updates: Developer and User Settings
Auto-update systems in mobile and desktop applications rely on a combination of platform-specific configurations, developer-defined policies, and user preferences. Properly configuring these settings ensures seamless updates while balancing security, performance, and user experience. Developers must leverage platform APIs, manifest files, and programmatic controls to enforce update behaviors, while users may adjust system-level permissions to influence how updates are applied. This section examines the technical configurations available in Android, iOS, and cross-platform environments, along with practical implementations for enforcing silent or mandatory updates.
Android and iOS enforce auto-update behaviors through declarative configurations in their respective manifest files. These settings define default update policies, permissions, and user interaction requirements.Android (`AndroidManifest.xml`)
Key attributes for auto-update control include:
`android:allowBackup`: Determines whether app data can be backed up, indirectly affecting update rollback safety.
`android:fullBackupContent`: Specifies directories to include in backups, impacting data persistence during updates.
`android:requestLegacyExternalStorage`: Legacy storage access may interfere with update mechanisms in newer Android versions.
`android:usesCleartextTraffic`: Disabling cleartext traffic ensures secure update payloads (HTTPS-only).
Custom permissions: Define granular control over update-related operations (e.g., `android.permission.REQUEST_INSTALL_PACKAGES` for silent installs on Android 6.0+).iOS (`Info.plist`)
Critical keys for update behavior include:
`UIApplicationOpenSettingsURLString`: Redirects users to app settings, useful for prompting manual update approvals.
`NSAppTransportSecurity`: Enforces secure connections for update servers (e.g., blocking HTTP).
`LSMinimumSystemVersion`: Ensures compatibility checks during updates.
`UIBackgroundModes`: If enabled, allows background fetch of update metadata (requires explicit user consent).
`SKStoreReviewController`: Facilitates in-app review prompts, which can indirectly influence update adoption.Cross-Platform Considerations
For frameworks like Flutter, React Native, or Cordova, developers must:
Extend native configurations via platform channels (e.g., `MainActivity.kt` for Android, `AppDelegate.swift` for iOS).
Use plugins like `flutter_appupdate` (Android/iOS) to abstract update logic while adhering to platform constraints.
Programmatic Enforcement of Update Policies
Developers can programmatically enforce update behaviors using platform SDKs. Below are pseudo-code examples for silent vs. user-initiated updates.Silent Updates (Android) // Check for updates in background (using Firebase App Distribution or custom server)
if (isUpdateAvailable()) {
try {
// Request silent install (Android 6.0+)
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(
Uri.parse("https://example.com/app.apk"),
"application/vnd.android.package-archive"
);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent); // Alternative: Use PackageInstaller API (requires user confirmation for system apps)
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL
);
int sessionId = packageInstaller.createSession(params);
// Write APK to session and commit...
} catch (Exception e) {
Log.e("UpdateError", "Silent install failed: " + e.getMessage());
}
} User-Initiated Updates (iOS) // Prompt user to update via App Store (SwiftUI example)
Button("Check for Updates") {
if let url = URL(string: "https://apps.apple.com/app/idYOUR_APP_ID") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
} // Background fetch for update metadata (requires entitlements)
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if let updateInfo = fetchUpdateMetadata() {
// Notify user or trigger silent download if allowed
completionHandler(.newData)
} else {
completionHandler(.noData)
}
} Key Differences
Android: Silent installs require `REQUEST_INSTALL_PACKAGES` permission (user-granted) or `PackageInstaller` API (system-level).
iOS: Silent updates are restricted; only metadata can be fetched in the background. Full updates require App Store redirection.
Windows/macOS: Use `WinRT` (Windows) or `Sparkle` (macOS) frameworks for silent updates, with system-level permissions for auto-downloads.
The following table summarizes default behaviors, customizable flags, and update frequency limits for major platforms. Values are based on public documentation as of 2023.
| Platform |
Default Behavior |
Customizable Flags |
Update Frequency Limits |
| Android |
- User-initiated updates via Play Store.
- Silent installs require explicit permissions.
- Auto-update disabled by default for system apps.
|
android:allowBackup="false" (disables data backup during updates).
android:usesCleartextTraffic="false" (enforces HTTPS for updates).
- Custom permissions:
REQUEST_INSTALL_PACKAGES, FOREGROUND_SERVICE.
|
- No hard limit; governed by user settings and Play Store policies.
- Silent updates: Max 1 per hour (API restriction).
- Foreground updates: No enforced limit.
|
| iOS |
- Updates require App Store redirection.
- Background fetch limited to metadata (no silent installs).
- Delta updates supported via App Store (iOS 16+).
|
UIBackgroundModes (enable background fetch).
NSAppTransportSecurity (HTTPS enforcement).
SKStoreReviewController (post-update review prompts).
|
- Metadata fetch: Max 1 per hour (background mode).
- Delta updates: No enforced limit (App Store controlled).
- Full updates: User-initiated (no platform limit).
|
| Windows (UWP) |
- Silent updates via Windows Store.
- Auto-update enabled by default for Store apps.
- Sideloaded apps require manual updates.
|
Extensions (declare update triggers in `Package.appxmanifest`).
AutoUpdate flag in `Package` node.
- Custom policies via Group Policy (Enterprise).
|
- Store apps: Daily checks (configurable via policy).
- Sideloaded: No enforced limit (developer-controlled).
|
| macOS |
- Silent updates via Sparkle framework.
- App Store updates require user confirmation.
- Enterprise apps can enforce mandatory updates.
|
SUFeedURL (Sparkle: RSS feed for updates).
SUEnableAutomaticChecks (boolean flag).
- <
Handling Update Challenges: Errors, Rollbacks, and Compatibility
Auto-update systems enhance user experience by ensuring apps remain functional and secure, but they introduce complexities in error handling, version management, and compatibility. Failures during updates—such as corrupted downloads, insufficient permissions, or storage constraints—can degrade performance or render apps unusable. Proactive strategies for error mitigation, rollback mechanisms, and compatibility checks are essential to maintain reliability. This section examines common failure points, their technical solutions, and the trade-offs between update methods to optimize performance and battery efficiency.
Common Failure Points in Auto-Updates
Auto-updates can encounter systemic failures at multiple stages, from download initiation to post-installation validation. Identifying these points allows developers to implement targeted fixes and improve robustness.Download and Installation Failures
Corrupted downloads or interrupted transfers often stem from unstable network conditions, server-side issues, or insufficient storage. Permission denials (e.g., restricted directories or missing write access) further complicate installation. Below are structured troubleshooting steps for each scenario:
-
Corrupted Downloads
Use checksum validation (SHA-256, MD5) to detect partial or tampered files before proceeding with installation. Implement retry logic with exponential backoff to handle transient network issues.
Checksum Validation Example (SHA-256):
// Pseudocode for SHA-256 verification
function verifyUpdateIntegrity(filePath, expectedChecksum) {
const actualChecksum = sha256(filePath);
return (actualChecksum === expectedChecksum);
}
Note: Always use cryptographically secure hashing (e.g., SHA-256) over MD5 for integrity checks.
-
Permission Denials
Preflight checks should verify write permissions in the target directory (`/data/data/` on Android, `~/Library/Application Support/` on macOS). For system-level apps, use `sudo` or elevated privileges (with user consent) during installation.
-
Storage Limits
Monitor available storage via platform APIs (e.g., `StorageManager` on Android, `NSFileManager` on iOS) and prompt users to free space if thresholds are exceeded. For large updates, offer optional delta patches to reduce payload size.
-
Dependency Conflicts
Use dependency resolvers (e.g., `npm` for JavaScript, `CocoaPods` for iOS) to detect version mismatches in libraries. Implement pre-update hooks to validate compatibility with existing dependencies.
Post-Installation Issues
Even after successful installation, updates may fail silently due to:
- Missing configuration files (e.g., `config.json` not copied).
- Database schema migrations that break existing data.
- Native library incompatibilities (e.g., ARM vs. x86 binaries).
Mitigation involves:
- Atomic Updates: Use temporary directories and atomic swaps (e.g., `mv` on Unix-like systems) to replace old binaries only after full validation.
- Backup and Restore: Automatically back up critical files (e.g., `AppData/Roaming`) before updates and restore them if the update fails.
- Logging and Rollback Triggers: Log update steps and implement health checks (e.g., crash reports, feature flags) to trigger rollbacks automatically.
Rollback Mechanisms and Version Pinning
Rollback strategies minimize disruption by reverting to a stable version when updates introduce critical failures. Version pinning (locking to a specific version) and delta updates (applying incremental changes) are key techniques to balance reliability and user convenience.Version Pinning Strategies
Version pinning ensures users can revert to a known-good state without manual intervention. Implementations include:
- Configuration-Driven Rollbacks: Store the last stable version in `app preferences` or a server-side registry. If an update fails post-installation, the app can auto-revert to the pinned version.
Example: Version Pinning in Android (Manifest)
<meta-data
android:name="com.example.app.last_stable_version"
android:value="3.2.1" />
Use shared preferences or a lightweight database to track pinned versions across sessions.
- A/B Testing with Fallback: Deploy updates to a subset of users (e.g., 10%) and monitor crash reports. If failures exceed a threshold (e.g., 5% crash rate), trigger a rollback for all users.
Delta Updates vs. Full Reinstalls
Delta updates reduce payload size and battery drain by transmitting only changed files, while full reinstalls ensure consistency but consume more resources. The trade-offs are summarized below:
| Metric |
Delta Updates |
Full Reinstalls |
| Payload Size |
5–50% smaller (depends on app size and changes) |
100% of app size |
| Battery Impact |
Lower (reduced data transfer and CPU usage) |
Higher (full verification and extraction) |
| Installation Time |
Faster (parallelizable patches) |
Slower (sequential operations) |
| Compatibility Risk |
Higher (partial updates may break dependencies) |
Lower (self-contained packages) |
| Use Case |
Frequent, incremental updates (e.g., bug fixes) |
Major versions, architecture changes (e.g., Android 12+) |
Data-Driven Insights
A study by Google (2022) found that delta updates reduced average installation time by 30% for apps under 100MB, with a 20% decrease in battery drain during updates. However, delta updates introduced a 15% higher crash rate due to partial dependency conflicts, highlighting the need for robust validation.Implementation Recommendations
- Use binary diffing tools (e.g., `xdelta3`, `bsdiff`) to generate delta patches.
- For critical apps (e.g., banking, healthcare), default to full reinstalls unless delta updates are validated through A/B testing.
- Monitor update success rates via analytics (e.g., Firebase Crashlytics) to adjust strategies dynamically.
Auto-updates must account for OS version fragmentation, hardware differences, and regional regulations. Below are structured approaches to address these challenges:OS-Specific Considerations
- Android: Use `minSdkVersion` and `targetSdkVersion` to gate updates. Leverage `Android App Bundles` to deliver optimized APKs per device configuration.
- iOS: Enforce `Deployment Target` compatibility and test on all supported iOS versions (e.g., iOS 14+ for SwiftUI apps).
- Desktop (Windows/macOS/Linux): Check for system libraries (e.g., `GLIBC` on Linux) and provide fallback mechanisms for unsupported versions.
Hardware Acceleration and ABI Compatibility
- ARM vs. x86: Bundle both architectures in a universal binary (e.g., `.app` on macOS) or use dynamic linking to avoid duplication.
- GPU Drivers: Validate OpenGL/Vulkan compatibility via `glGetString` or `vkEnumeratePhysicalDevices` before rendering updates.
Regulatory and Localization Constraints
- GDPR/CCPA: Ensure update payloads do not transmit personal data. Use encrypted diffs for sensitive configurations.
- Language Packs: Support dynamic language updates via `Resource Bundles` (e.g., `Localizable.strings` on iOS) without full app reinstalls.
Cross-Platform Update Orchestration
Use a unified update server (e.g., AWS S3 + CloudFront) to distribute platform-specific binaries. Example workflow: - Client requests update metadata (version, checksums, platform tags).
- Server responds with a manifest linking to the appropriate binary (e.g., `app-android-arm64.apk`, `app-macos-universal.dmg`).
- Client downloads and verifies the binary before installation.
Example: Cross-Platform Manifest (JSON)
{
"version": "4.1.0
Ensuring the reliability of auto-update mechanisms requires rigorous testing to validate functionality under diverse conditions, from ideal scenarios to extreme edge cases. A structured QA approach minimizes disruptions, reduces user-facing failures, and guarantees seamless transitions between app versions. This section outlines a comprehensive checklist for manual and automated testing, tools for simulating real-world constraints, and metrics tracking to monitor post-deployment performance.
Checklist for Validating Auto-Update Functionality
A well-defined test suite covers core workflows, edge cases, and failure scenarios to ensure robustness. Below is a categorized checklist for manual and automated validation:
-
Core Update Workflows
- Verify successful download, verification, and installation of updates under normal network conditions (Wi-Fi, 4G/5G).
- Test update initiation from different app states (foreground, background, suspended).
- Confirm rollback to the previous version if the update fails post-installation.
- Validate update progress reporting (e.g., percentage, estimated time remaining) and cancellation mid-process.
- Ensure update metadata (version code, hash, changelog) is accurately fetched and compared.
-
Edge Cases and Constraints
- Low Storage Conditions
- Simulate insufficient disk space (<50MB free) and observe error handling (graceful degradation, user prompts).
- Test behavior when storage is freed mid-update (e.g., via external SD card or cloud storage cleanup).
- Network Instability
- Interrupt downloads at various stages (0%, 50%, 99%) and verify retry logic or fallback mechanisms.
- Test updates on fluctuating connections (e.g., 3G → Wi-Fi → 2G) without corruption.
- Validate timeout thresholds for update checks (e.g., 10s, 30s) and user notifications.
- Concurrent Operations
- Trigger multiple updates simultaneously (e.g., two devices on the same network) and check for conflicts or resource contention.
- Test updates during active app sessions (e.g., real-time gaming, VoIP calls) without disrupting critical functionality.
- Device-Specific Constraints
- Verify updates on low-end devices (e.g., 1GB RAM, Android Go) with restricted background processes.
- Test on rooted/jailbroken devices where update signatures or permissions may be bypassed.
- Confirm compatibility with custom ROMs (e.g., LineageOS) or manufacturer skins (e.g., MIUI, EMUI).
- Security and Integrity
- Attempt tampering with update files (e.g., corrupting hashes, modifying binaries) and validate rejection or rollback.
- Test updates signed with expired or revoked certificates.
- Verify integrity checks for incremental updates (delta patches) against full binary updates.
-
User Experience and Notifications
- Ensure update prompts are clear, non-intrusive, and localized (e.g., mandatory vs. optional updates).
- Test silent updates (background-only) without user interaction and confirm functionality retention.
- Validate fallback mechanisms when updates fail silently (e.g., retry after 24 hours).
- Check accessibility compliance (e.g., screen reader support for update dialogs).
Manual testing alone cannot cover the scale of devices, OS versions, and network conditions. Automation leverages cloud-based distribution platforms and CI/CD pipelines to accelerate validation while reducing human error. Below are key tools and their integration strategies:
-
Firebase App Distribution
Firebase App Distribution enables closed beta testing with direct OTA (Over-The-Air) updates to physical devices via a web interface or CLI. Ideal for pre-release validation of auto-update logic.
- Integration Workflow:
- Upload test builds (APK/IPA) with embedded auto-update metadata (e.g., version code, update URL).
- Distribute builds to testers with specific device filters (e.g., Android 12+, iOS 15+).
- Trigger updates programmatically via Firebase SDK or manual prompts.
- Monitor crash reports and update success rates in Firebase Console.
- Limitations:
- Requires manual setup for custom update servers (not native to Firebase).
- Limited to 30 testers per distribution (scalability constraints).
-
TestFlight (Apple) and Google Play Beta Testing
- TestFlight:
- Supports up to 10,000 external testers with 90-day validity for iOS/macOS apps.
- Auto-update testing requires embedding App Store receipt validation logic to simulate OTA updates.
- Use Xcode Cloud or Fastlane to automate build distribution and update triggers.
- Google Play Beta:
- Allows open/closed beta testing with up to 100,000 users.
- Auto-update validation involves testing the Play Core Library’s `UpdateManager` with staged rollouts.
- Leverage Google Play Console’s "App Bundle Explorer" to verify update compatibility.
-
Custom CI/CD Pipelines (Jenkins, GitHub Actions)
CI/CD pipelines automate build, test, and deployment cycles, including synthetic update scenarios. Tools like Jenkins or GitHub Actions can simulate network conditions, deploy test builds, and validate outcomes.
- Example Pipeline (GitHub Actions):
- Trigger on `git push` to a `release-candidate` branch.
- Build APK/IPA with embedded update manifest (e.g., `update.json`).
- Deploy to a test server (e.g., AWS S3, Firebase Hosting).
- Use `act` or `gh` CLI to run local emulators with network throttling.
- Execute automated tests (e.g., Espresso/UIAutomator for Android, XCTest for iOS) to validate update flows.
- Aggregate logs and generate reports via Slack/email notifications.
- Key Plugins:
- Android: `fastlane supply` for Play Store updates, `detekt` for static code analysis.
- iOS: `xcodebuild` for archive validation, `sigh` for notarization.
- Network Simulation: `tc` (Linux), `Network Link Conditioner` (macOS), or `Charles Proxy` for HTTP throttling.
Simulating Network Conditions for Resilience Testing
Real-world networks exhibit latency, packet loss, and bandwidth fluctuations that can disrupt updates. Scripts to replicate these conditions enable controlled testing of fallback mechanisms and retry logic. Below are Python and Bash examples for Linux/macOS environments:
-
Python Script for Network Throttling (using `subprocess` and `scapy`)
This script uses `tc` (Linux) or `Network Link Conditioner` (macOS) to simulate poor connectivity during update downloads. Replace `UPDATE_URL` with the target server endpoint.
#!/usr/bin/env python3
import subprocess
import time
import argparsedef simulate_network(interface, latency_ms=500, packet_loss=20, bandwidth_kbps User Experience and Communication in Auto-Updates
Auto-updates enhance app functionality, security, and performance but require careful UX design to ensure transparency, user trust, and seamless integration. Effective communication about updates—balancing automation with user control—reduces friction while maintaining engagement. This section explores best practices for notification timing, messaging strategies, and handling user preferences, alongside comparative analyses of industry-leading approaches.
Designing Update Notification Strategies
Update notifications must align with user context to avoid disruption. Timing plays a critical role: updates during idle periods (e.g., overnight or when the app is inactive) minimize interference, while critical security patches may warrant immediate prompts. Messaging tone should prioritize clarity—avoiding technical jargon while emphasizing benefits (e.g., "New features for smoother video calls") over obligations (e.g., "Update required").User behavior data informs optimal delivery:
- Idle sessions: Ideal for non-critical updates (e.g., performance optimizations).
- Active sessions: Reserved for urgent fixes (e.g., security vulnerabilities) with minimal disruption (e.g., a small, non-intrusive banner).
- Offline users: Queue updates for the next active session with a confirmation prompt on reconnection.
Key principles for messaging:
- Benefit-driven: Highlight improvements (e.g., "Faster load times with the latest update").
- Action-oriented: Use clear CTAs (e.g., "Update Now" vs. vague "Learn More").
- Transparency: Disclose reasons for updates (e.g., "Bug fixes for stability").
- Fallback options: Provide alternatives for users who disable auto-updates (e.g., manual triggers via settings).
Wireframe: In-App Update Notification Banner
A well-structured notification banner balances visibility and non-intrusiveness. Below is a text-based wireframe for a non-modal, dismissible banner appearing at the bottom of the screen during idle periods:```
+-----------------------------------------------------+
| [App Logo] |
| ▶ New Update Available (v3.2) |
| • Bug fixes for crashes during file sharing |
| • Improved battery efficiency |
| |
| [Update Now] [Remind Me Later] [Disable Updates] |
| [×] (Close) |
+-----------------------------------------------------+
``` Design considerations:
- Placement: Bottom-center to avoid obstructing core UI (e.g., chat threads in messaging apps).
- Visual hierarchy: Logo and version number for quick recognition; bullet points for concise benefits.
- Button functionality:
- Update Now: Triggers immediate download/install (prioritized for critical updates).
- Remind Me Later: Stores preference (e.g., via `SharedPreferences` or `UserDefaults`) to reappear after 24 hours.
- Disable Updates: Persists user choice securely (see Handling User Opt-Outs).
- Dismissibility: "×" button allows users to close without action, reducing abandonment.
Accessibility notes:
- High-contrast text for visibility.
- Screen reader support (e.g., ARIA labels for buttons).
- Localized language and region-specific icons (e.g., battery symbols for efficiency updates).
Handling User Opt-Outs Gracefully
Users who disable auto-updates require alternative pathways to stay informed and updated. Secure preference storage ensures consistency across devices and sessions:
- Local storage: Encrypted keys for opt-out flags (e.g., `SecurePreferences` in Android or `Keychain` in iOS).
- Server-side validation: Sync preferences during login to prevent bypassing (e.g., via API calls to `user_preferences/update`).
Manual update triggers should be intuitive:
- Settings menu: Dedicated "Updates" section with:
- Version history.
- Direct download links (e.g., "Check for Updates" button).
- Release notes toggle (for users who prefer transparency).
- In-app prompts: Periodic reminders (e.g., "Your app is outdated. Tap to update.") with a clear CTA.
Fallback workflow for disabled auto-updates:
1. User disables auto-updates → preference stored locally/remotely.
2. New update released → server flags user as "manual-only."
3. User accesses app → settings menu highlights outdated version with update link.
4. Optional: Background service checks for updates weekly and notifies via push (if permissions granted).
Comparison of Update Communication Strategies
Different apps employ distinct approaches to balance automation and user control. Below is a comparative analysis of WhatsApp (silent updates) and Instagram (pop-up prompts), evaluating effectiveness based on user adoption and feedback.
| Metric | WhatsApp (Silent Updates) | Instagram (Pop-Up Prompts) |
| Update Trigger | Automatic, background (no user interaction). | Manual prompt during active use (modal/banner). |
| User Control | Limited: Users can only opt out via settings. | High: "Update Now," "Remind Me," "No Thanks." |
| Notification Timing | Idle periods (e.g., overnight). | Active sessions (e.g., after 30 seconds of inactivity). |
| Messaging Tone | Minimalist (no in-app messages). | Benefit-focused (e.g., "New features for Stories"). |
| Adoption Rate | ~95%+ (high due to lack of friction). | ~80% (some users dismiss prompts). |
| User Feedback | Rare complaints; perceived as seamless. | Mixed: some users find prompts disruptive. |
| Critical Updates | Pushed immediately (e.g., security patches). | Prioritized with urgent banners (e.g., "Update to fix bugs"). |
| Opt-Out Handling | One-time setting (no reminders). | Persistent reminders (e.g., weekly notifications). |
Key insights:
- WhatsApp’s approach prioritizes frictionless updates, leveraging its core value (reliability) to justify minimal user interaction. Silent updates reduce abandonment but may lead to confusion if users notice changes without context (e.g., UI tweaks).
- Instagram’s prompts balance control and engagement, using social proof (e.g., "Join millions using the new feature") to incentivize updates. However, overuse of pop-ups risks fatigue, as seen in user complaints about "update spam."
Hybrid strategy recommendations:
- For consumer apps: Use non-modal banners with clear benefits and idle-period timing (like Instagram) but limit frequency to avoid annoyance.
- For enterprise/utility apps: Adopt silent updates with optional in-app release notes (like WhatsApp) to minimize disruption.
- For high-stakes updates (e.g., security): Combine urgent pop-ups with a fallback email/SMS for users who dismiss notifications.
Implementing a flawless auto-update mechanism transcends mere technical execution; it demands a holistic approach that harmonizes backend efficiency with user trust. From configuring granular platform policies in `AndroidManifest.xml` or `Info.plist` to simulating adversarial network conditions for resilience testing, every layer of the process contributes to a system that is both adaptive and user-friendly. By leveraging checksum validation, incremental patches, and data-driven monitoring tools, developers can minimize downtime while maximizing adoption rates. Ultimately, the most successful auto-update strategies prioritize transparency—whether through timed notifications or opt-out safeguards—ensuring users remain informed without compromising the seamless experience they expect from modern applications.
|
|
Leave a Comment
Comments are moderated before appearing. The data you submit is processed according to the Privacy Policy of ir.msu.ac.zw.