Skip to content

Commit

Permalink
feat(package_info_plus): add update time (#3466)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew-Bekhiet authored Feb 20, 2025
1 parent 1cbf2b5 commit c0ab921
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 69 deletions.
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

0 comments on commit c0ab921

Please sign in to comment.