Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin {
val buildSignature = getBuildSignature(packageManager)

val installerPackage = getInstallerPackageName()
val installTimeMillis = getInstallTimeMillis()

val installTimeMillis = info.firstInstallTime
val updateTimeMillis = info.lastUpdateTime

val infoMap = HashMap<String, String>()
infoMap.apply {
Expand All @@ -49,7 +51,8 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin {
put("buildNumber", getLongVersionCode(info).toString())
if (buildSignature != null) put("buildSignature", buildSignature)
if (installerPackage != null) put("installerStore", installerPackage)
if (installTimeMillis != null) put("installTime", installTimeMillis.toString())
put("installTime", installTimeMillis.toString())
put("updateTime", updateTimeMillis.toString())
}.also { resultingMap ->
result.success(resultingMap)
}
Expand All @@ -76,22 +79,6 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin {
}
}

private fun getInstallTimeMillis(): Long? {
return try {
val packageManager = applicationContext!!.packageManager
val packageName = applicationContext!!.packageName
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
} else {
packageManager.getPackageInfo(packageName, 0)
}

packageInfo.firstInstallTime
} catch (e: PackageManager.NameNotFoundException) {
null
}
}

@Suppress("deprecation")
private fun getLongVersionCode(info: PackageInfo): Long {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ void main() {
expect(info.version, '1.2.3');
expect(info.installerStore, null);
expect(info.installTime, null);
expect(info.updateTime, null);
} else {
if (Platform.isAndroid) {
final androidVersionInfo = await DeviceInfoPlugin().androidInfo;
Expand All @@ -52,6 +53,14 @@ void main() {
lessThanOrEqualTo(1),
),
);
expect(
info.updateTime,
isA<DateTime>().having(
(d) => d.difference(DateTime.now()).inMinutes,
'Was just updated',
lessThanOrEqualTo(1),
),
);
} else if (Platform.isIOS) {
expect(info.appName, 'Package Info Plus Example');
expect(info.buildNumber, '4');
Expand All @@ -67,6 +76,14 @@ void main() {
lessThanOrEqualTo(1),
),
);
expect(
info.updateTime,
isA<DateTime>().having(
(d) => d.difference(DateTime.now()).inMinutes,
'Was just updated',
lessThanOrEqualTo(1),
),
);
} else if (Platform.isMacOS) {
expect(info.appName, 'Package Info Plus Example');
expect(info.buildNumber, '4');
Expand All @@ -82,6 +99,14 @@ void main() {
lessThanOrEqualTo(1),
),
);
expect(
info.updateTime,
isA<DateTime>().having(
(d) => d.difference(DateTime.now()).inMinutes,
'Was just updated',
lessThanOrEqualTo(1),
),
);
} else if (Platform.isLinux) {
expect(info.appName, 'package_info_plus_example');
expect(info.buildNumber, '4');
Expand All @@ -96,6 +121,14 @@ void main() {
lessThanOrEqualTo(1),
),
);
expect(
info.updateTime,
isA<DateTime>().having(
(d) => d.difference(DateTime.now()).inMinutes,
'Was just updated',
lessThanOrEqualTo(1),
),
);
} else if (Platform.isWindows) {
expect(info.appName, 'example');
expect(info.buildNumber, '4');
Expand All @@ -111,6 +144,14 @@ void main() {
lessThanOrEqualTo(1),
),
);
expect(
info.updateTime,
isA<DateTime>().having(
(d) => d.difference(DateTime.now()).inMinutes,
'Was just updated',
lessThanOrEqualTo(1),
),
);
} else {
throw (UnsupportedError('platform not supported'));
}
Expand All @@ -127,6 +168,7 @@ void main() {
expect(find.text('Not set'), findsOneWidget);
expect(find.text('not available'), findsOneWidget);
expect(find.text('Install time not available'), findsOneWidget);
expect(find.text('Update time not available'), findsOneWidget);
} else {
final expectedInstallTimeIso = testStartTime.toIso8601String();
final installTimeRegex = RegExp(
Expand All @@ -153,7 +195,7 @@ void main() {
} else {
expect(find.text('not available'), findsOneWidget);
}
expect(find.textContaining(installTimeRegex), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
} else if (Platform.isIOS) {
expect(find.text('Package Info Plus Example'), findsOneWidget);
expect(find.text('4'), findsOneWidget);
Expand All @@ -162,7 +204,7 @@ void main() {
expect(find.text('1.2.3'), findsOneWidget);
expect(find.text('Not set'), findsOneWidget);
expect(find.text('com.apple.simulator'), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
} else if (Platform.isMacOS) {
expect(find.text('Package Info Plus Example'), findsOneWidget);
expect(find.text('4'), findsOneWidget);
Expand All @@ -171,20 +213,20 @@ void main() {
expect(find.text('1.2.3'), findsOneWidget);
expect(find.text('Not set'), findsOneWidget);
expect(find.text('not available'), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
} else if (Platform.isLinux) {
expect(find.text('package_info_plus_example'), findsNWidgets(2));
expect(find.text('1.2.3'), findsOneWidget);
expect(find.text('4'), findsOneWidget);
expect(find.text('Not set'), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
} else if (Platform.isWindows) {
expect(find.text('example'), findsNWidgets(2));
expect(find.text('1.2.3'), findsOneWidget);
expect(find.text('4'), findsOneWidget);
expect(find.text('Not set'), findsOneWidget);
expect(find.text('not available'), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsOneWidget);
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
} else {
throw (UnsupportedError('platform not supported'));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ class _MyHomePageState extends State<MyHomePage> {
_packageInfo.installTime?.toIso8601String() ??
'Install time not available',
),
_infoTile(
'Update time',
_packageInfo.updateTime?.toIso8601String() ??
'Update time not available',
),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call
? @"com.apple.testflight"
: @"com.apple";

NSURL* urlToDocumentsFolder = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
__autoreleasing NSError *error;
NSDate *installDate = [[[NSFileManager defaultManager] attributesOfItemAtPath:urlToDocumentsFolder.path error:&error] objectForKey:NSFileCreationDate];
NSNumber *installTimeMillis = installDate ? @((long long)([installDate timeIntervalSince1970] * 1000)) : [NSNull null];

NSDate *installDate = [self getInstallDate];
NSDate *updateDate = [self getUpdateDate];

result(@{
@"appName" : [[NSBundle mainBundle]
Expand All @@ -46,12 +43,46 @@ - (void)handleMethodCall:(FlutterMethodCall *)call
objectForInfoDictionaryKey:@"CFBundleVersion"]
?: [NSNull null],
@"installerStore" : installerStore,
@"installTime" : installTimeMillis ? [installTimeMillis stringValue] : [NSNull null]
@"installTime" : [self getTimeMillisStringFromDate:installDate] ?: [NSNull null],
@"updateTime" : [self getTimeMillisStringFromDate:updateDate] ?: [NSNull null]
});

} else {
result(FlutterMethodNotImplemented);
}
}

- (NSDate *)getInstallDate {
NSURL* urlToDocumentsFolder = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
__autoreleasing NSError *error;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:urlToDocumentsFolder.path error:&error];

if (error) {
return nil;
}

return [attributes objectForKey:NSFileCreationDate];
}

- (NSDate *)getUpdateDate {
__autoreleasing NSError *error;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[[NSBundle mainBundle] bundlePath] error:&error];
NSDate *updateDate = [attributes fileModificationDate];

if (error) {
return nil;
}

return updateDate;
}

- (NSString *)getTimeMillisStringFromDate:(NSDate *)date {
if (!date) {
return nil;
}

NSNumber *timeMillis = @((long long)([date timeIntervalSince1970] * 1000));
return [timeMillis stringValue];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class PackageInfo {
this.buildSignature = '',
this.installerStore,
this.installTime,
this.updateTime,
});

static PackageInfo? _fromPlatform;
Expand Down Expand Up @@ -90,6 +91,7 @@ class PackageInfo {
buildSignature: platformData.buildSignature,
installerStore: platformData.installerStore,
installTime: platformData.installTime,
updateTime: platformData.updateTime,
);
return _fromPlatform!;
}
Expand Down Expand Up @@ -159,6 +161,15 @@ class PackageInfo {
/// - On web, returns `null`.
final DateTime? installTime;

/// The time when the application was last updated.
///
/// - On Android, returns `PackageManager.lastUpdateTime`
/// - On iOS and macOS, return the last modified date of the app main bundle
/// - On Windows and Linux, returns the last modified date of the app executable.
/// If the last modified date is not available, returns `null`.
/// - On web, returns `null`.
final DateTime? updateTime;

/// Initializes the application metadata with mock values for testing.
///
/// If the singleton instance has been initialized already, it is overwritten.
Expand All @@ -171,6 +182,7 @@ class PackageInfo {
required String buildSignature,
String? installerStore,
DateTime? installTime,
DateTime? updateTime,
}) {
_fromPlatform = PackageInfo(
appName: appName,
Expand All @@ -180,6 +192,7 @@ class PackageInfo {
buildSignature: buildSignature,
installerStore: installerStore,
installTime: installTime,
updateTime: updateTime,
);
}

Expand All @@ -195,7 +208,8 @@ class PackageInfo {
buildNumber == other.buildNumber &&
buildSignature == other.buildSignature &&
installerStore == other.installerStore &&
installTime == other.installTime;
installTime == other.installTime &&
updateTime == other.updateTime;

/// Overwrite hashCode for value equality
@override
Expand All @@ -206,11 +220,12 @@ class PackageInfo {
buildNumber.hashCode ^
buildSignature.hashCode ^
installerStore.hashCode ^
installTime.hashCode;
installTime.hashCode ^
updateTime.hashCode;

@override
String toString() {
return 'PackageInfo(appName: $appName, buildNumber: $buildNumber, packageName: $packageName, version: $version, buildSignature: $buildSignature, installerStore: $installerStore, installTime: $installTime)';
return 'PackageInfo(appName: $appName, buildNumber: $buildNumber, packageName: $packageName, version: $version, buildSignature: $buildSignature, installerStore: $installerStore, installTime: $installTime, updateTime: $updateTime)';
}

Map<String, dynamic> _toMap() {
Expand All @@ -222,6 +237,7 @@ class PackageInfo {
if (buildSignature.isNotEmpty) 'buildSignature': buildSignature,
if (installerStore?.isNotEmpty ?? false) 'installerStore': installerStore,
if (installTime != null) 'installTime': installTime!.toIso8601String(),
if (updateTime != null) 'updateTime': updateTime!.toIso8601String(),
};
}

Expand Down
Loading
Loading