getAppLinksStatus static method
Get app links verification status
Implementation
static Future<VerificationResult> getAppLinksStatus(
String packageName,
) async {
if (!await isAdbAvailable()) {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.skipped,
message: 'ADB not available',
fixSuggestion:
'Install Android SDK Platform Tools and ensure adb is in PATH',
);
}
if (!await isDeviceConnected()) {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.skipped,
message: 'No Android device/emulator connected',
fixSuggestion: 'Connect an Android device or start an emulator',
);
}
try {
// Check Android version first
final sdkVersionResult = await Process.run('adb', [
'shell',
'getprop',
'ro.build.version.sdk',
]);
final sdkVersion =
int.tryParse((sdkVersionResult.stdout as String).trim()) ?? 0;
final supportsGetAppLinks = sdkVersion >= 31; // Android 12+
if (!supportsGetAppLinks) {
// For Android < 12, check if app links are configured using dumpsys
// First check if app has domain URLs configured
final dumpsysResult = await Process.run('adb', [
'shell',
'dumpsys',
'package',
packageName,
]);
final hasDomainUrls =
(dumpsysResult.stdout as String).contains('HAS_DOMAIN_URLS');
if (!hasDomainUrls) {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.warning,
message: 'App links not configured in AndroidManifest.xml',
fixSuggestion:
'Add intent filters with android:autoVerify="true" to AndroidManifest.xml',
details: {'packageName': packageName},
);
}
// App has domain URLs configured, check user selection preference
final result = await Process.run('adb', [
'shell',
'pm',
'get-app-link',
packageName,
]);
if (result.exitCode == 0) {
final output = result.stdout as String;
// Parse the output (format: "always", "ask", "never", or "undefined")
final status = output.trim().toLowerCase();
if (status == 'always') {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.success,
message: 'App links enabled. User selection: always open in app',
details: {
'packageName': packageName,
'userSelection': status,
'androidVersion':
'Android ${sdkVersion < 21 ? "Lollipop" : sdkVersion < 23 ? "Marshmallow" : sdkVersion < 26 ? "Nougat" : sdkVersion < 28 ? "Oreo" : sdkVersion < 29 ? "Pie" : sdkVersion < 30 ? "10" : sdkVersion < 31 ? "11" : "12+"}',
},
);
} else if (status == 'ask') {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.warning,
message: 'App links configured but user will be asked each time',
fixSuggestion:
'Run: adb shell pm set-app-link --package $packageName always',
details: {'packageName': packageName, 'userSelection': status},
);
} else if (status == 'undefined') {
// On Android < 12, "undefined" means no explicit user preference is set
// This is normal - app links still work based on domain verification
// The pm get-app-link command only tracks explicit user preferences, not verification status
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.success,
message:
'App links configured. On Android ${sdkVersion < 30 ? "10" : "11"}, links will open in app based on domain verification.',
details: {
'packageName': packageName,
'userSelection': status,
'appLinksConfigured': true,
'androidSdkVersion': sdkVersion,
'note':
'On Android < 12, pm get-app-link only tracks explicit user preferences. App links work based on domain verification.',
},
);
} else {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.warning,
message: 'App links user selection: $status',
fixSuggestion:
'Run: adb shell pm set-app-link --package $packageName always',
details: {'packageName': packageName, 'userSelection': status},
);
}
}
}
// For Android 12+, use get-app-links (plural)
final result = await Process.run('adb', [
'shell',
'pm',
'get-app-links',
packageName,
]);
if (result.exitCode != 0) {
final errorMsg = (result.stderr as String).trim();
final stdoutMsg = (result.stdout as String).trim();
final fullError = errorMsg.isNotEmpty
? errorMsg
: (stdoutMsg.isNotEmpty ? stdoutMsg : 'Unknown error');
// Check if app is installed
final installCheck = await Process.run('adb', [
'shell',
'pm',
'list',
'packages',
packageName,
]);
final isInstalled = installCheck.exitCode == 0 &&
(installCheck.stdout as String).contains('package:$packageName');
if (!isInstalled) {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.error,
message: 'App not installed on device. Package: $packageName',
fixSuggestion: 'Install the app on the device/emulator first',
);
}
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.warning,
message: 'Failed to get app links status: $fullError',
fixSuggestion:
'The app may not have app links configured. Check AndroidManifest.xml for intent filters.',
details: {
'packageName': packageName,
'error': fullError,
'exitCode': result.exitCode.toString(),
},
);
}
final output = result.stdout as String;
// Parse output to find verification status
// Format: "com.example.app:\n ID: ...\n Signatures: ...\n Domain verification state:\n example.com: verified"
final lines = output.split('\n');
final statusMap = <String, String>{};
String? currentDomain;
for (final line in lines) {
if (line.contains(':')) {
final parts = line.split(':');
if (parts.length >= 2) {
final key = parts[0].trim();
final value = parts.sublist(1).join(':').trim();
if (key.contains('Domain verification state') ||
key.contains('Verification state')) {
// Next lines will contain domain statuses
currentDomain = null;
} else if (currentDomain != null || key.contains('.')) {
// This might be a domain
if (key.contains('.')) {
currentDomain = key;
statusMap[currentDomain] = value;
}
}
}
}
}
// Check for verified status
final verifiedDomains = statusMap.entries
.where((e) => e.value.toLowerCase().contains('verified'))
.map((e) => e.key)
.toList();
if (verifiedDomains.isNotEmpty) {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.success,
message:
'App links verified for domains: ${verifiedDomains.join(", ")}',
details: {
'packageName': packageName,
'verifiedDomains': verifiedDomains,
'allStatuses': statusMap,
},
);
} else {
final allStatuses = statusMap.values.join(', ');
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.warning,
message: 'App links not verified. Status: $allStatuses',
fixSuggestion:
'Run: adb shell pm verify-app-links --re-verify $packageName\n'
'Then check the status again',
details: {'packageName': packageName, 'statuses': statusMap},
);
}
} catch (e) {
return VerificationResult(
checkName: 'Android Runtime Test - App Links Status',
status: VerificationStatus.error,
message: 'Error checking app links status: $e',
fixSuggestion: 'Ensure ADB is working and device is connected',
);
}
}