[path_provider] Use the application ID in the application support path (#2845) (#3077)

Use the existing executable named directory if it exists, to allow backwards
compatibility to work.
diff --git a/packages/path_provider/path_provider_linux/CHANGELOG.md b/packages/path_provider/path_provider_linux/CHANGELOG.md
index 209fddb..f227570 100644
--- a/packages/path_provider/path_provider_linux/CHANGELOG.md
+++ b/packages/path_provider/path_provider_linux/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.1.3
+
+* Change getApplicationSupportPath from using executable name to application ID (if provided).
+  * If the executable name based directory exists, continue to use that so existing applications continue with the same behaviour.
+
 ## 2.1.2
 
 * Fixes link in README.
diff --git a/packages/path_provider/path_provider_linux/lib/path_provider_linux.dart b/packages/path_provider/path_provider_linux/lib/path_provider_linux.dart
index ab18db6..e32af1b 100644
--- a/packages/path_provider/path_provider_linux/lib/path_provider_linux.dart
+++ b/packages/path_provider/path_provider_linux/lib/path_provider_linux.dart
@@ -2,61 +2,4 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:io';
-
-import 'package:flutter/foundation.dart';
-import 'package:path/path.dart' as path;
-import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
-import 'package:xdg_directories/xdg_directories.dart' as xdg;
-
-/// The linux implementation of [PathProviderPlatform]
-///
-/// This class implements the `package:path_provider` functionality for linux
-class PathProviderLinux extends PathProviderPlatform {
-  /// Constructs an instance of [PathProviderLinux]
-  PathProviderLinux() : _environment = Platform.environment;
-
-  /// Constructs an instance of [PathProviderLinux] with the given [environment]
-  @visibleForTesting
-  PathProviderLinux.private({
-    required Map<String, String> environment,
-  }) : _environment = environment;
-
-  final Map<String, String> _environment;
-
-  /// Registers this class as the default instance of [PathProviderPlatform]
-  static void registerWith() {
-    PathProviderPlatform.instance = PathProviderLinux();
-  }
-
-  @override
-  Future<String?> getTemporaryPath() {
-    final String environmentTmpDir = _environment['TMPDIR'] ?? '';
-    return Future<String?>.value(
-      environmentTmpDir.isEmpty ? '/tmp' : environmentTmpDir,
-    );
-  }
-
-  @override
-  Future<String?> getApplicationSupportPath() async {
-    final String processName = path.basenameWithoutExtension(
-        await File('/proc/self/exe').resolveSymbolicLinks());
-    final Directory directory =
-        Directory(path.join(xdg.dataHome.path, processName));
-    // Creating the directory if it doesn't exist, because mobile implementations assume the directory exists
-    if (!directory.existsSync()) {
-      await directory.create(recursive: true);
-    }
-    return directory.path;
-  }
-
-  @override
-  Future<String?> getApplicationDocumentsPath() {
-    return Future<String?>.value(xdg.getUserDirectory('DOCUMENTS')?.path);
-  }
-
-  @override
-  Future<String?> getDownloadsPath() {
-    return Future<String?>.value(xdg.getUserDirectory('DOWNLOAD')?.path);
-  }
-}
+export 'src/path_provider_linux.dart';
diff --git a/packages/path_provider/path_provider_linux/lib/src/get_application_id.dart b/packages/path_provider/path_provider_linux/lib/src/get_application_id.dart
new file mode 100644
index 0000000..e169c02
--- /dev/null
+++ b/packages/path_provider/path_provider_linux/lib/src/get_application_id.dart
@@ -0,0 +1,9 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// getApplicationId() is implemented using FFI; export a stub for platforms
+// that don't support FFI (e.g., web) to avoid having transitive dependencies
+// break web compilation.
+export 'get_application_id_stub.dart'
+    if (dart.library.ffi) 'get_application_id_real.dart';
diff --git a/packages/path_provider/path_provider_linux/lib/src/get_application_id_real.dart b/packages/path_provider/path_provider_linux/lib/src/get_application_id_real.dart
new file mode 100644
index 0000000..f6d25bb
--- /dev/null
+++ b/packages/path_provider/path_provider_linux/lib/src/get_application_id_real.dart
@@ -0,0 +1,42 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:ffi';
+import 'package:ffi/ffi.dart';
+
+// GApplication* g_application_get_default();
+typedef _GApplicationGetDefaultC = IntPtr Function();
+typedef _GApplicationGetDefaultDart = int Function();
+
+// const gchar* g_application_get_application_id(GApplication* application);
+typedef _GApplicationGetApplicationIdC = Pointer<Utf8> Function(IntPtr);
+typedef _GApplicationGetApplicationIdDart = Pointer<Utf8> Function(int);
+
+/// Gets the application ID for this app.
+String? getApplicationId() {
+  DynamicLibrary gio;
+  try {
+    gio = DynamicLibrary.open('libgio-2.0.so');
+  } on ArgumentError {
+    return null;
+  }
+  final _GApplicationGetDefaultDart gApplicationGetDefault =
+      gio.lookupFunction<_GApplicationGetDefaultC, _GApplicationGetDefaultDart>(
+          'g_application_get_default');
+  final int app = gApplicationGetDefault();
+  if (app == 0) {
+    return null;
+  }
+
+  final _GApplicationGetApplicationIdDart gApplicationGetApplicationId =
+      gio.lookupFunction<_GApplicationGetApplicationIdC,
+              _GApplicationGetApplicationIdDart>(
+          'g_application_get_application_id');
+  final Pointer<Utf8> appId = gApplicationGetApplicationId(app);
+  if (appId == null) {
+    return null;
+  }
+
+  return appId.toDartString();
+}
diff --git a/packages/path_provider/path_provider_linux/lib/src/get_application_id_stub.dart b/packages/path_provider/path_provider_linux/lib/src/get_application_id_stub.dart
new file mode 100644
index 0000000..9099976
--- /dev/null
+++ b/packages/path_provider/path_provider_linux/lib/src/get_application_id_stub.dart
@@ -0,0 +1,6 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Gets the application ID for this app.
+String? getApplicationId() => null;
diff --git a/packages/path_provider/path_provider_linux/lib/src/path_provider_linux.dart b/packages/path_provider/path_provider_linux/lib/src/path_provider_linux.dart
new file mode 100644
index 0000000..1544dce
--- /dev/null
+++ b/packages/path_provider/path_provider_linux/lib/src/path_provider_linux.dart
@@ -0,0 +1,92 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:flutter/foundation.dart';
+import 'package:path/path.dart' as path;
+import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
+import 'package:xdg_directories/xdg_directories.dart' as xdg;
+
+import 'get_application_id.dart';
+
+/// The linux implementation of [PathProviderPlatform]
+///
+/// This class implements the `package:path_provider` functionality for Linux.
+class PathProviderLinux extends PathProviderPlatform {
+  /// Constructs an instance of [PathProviderLinux]
+  PathProviderLinux() : _environment = Platform.environment;
+
+  /// Constructs an instance of [PathProviderLinux] with the given [environment]
+  @visibleForTesting
+  PathProviderLinux.private(
+      {Map<String, String> environment = const <String, String>{},
+      String? executableName,
+      String? applicationId})
+      : _environment = environment,
+        _executableName = executableName,
+        _applicationId = applicationId;
+
+  final Map<String, String> _environment;
+  String? _executableName;
+  String? _applicationId;
+
+  /// Registers this class as the default instance of [PathProviderPlatform]
+  static void registerWith() {
+    PathProviderPlatform.instance = PathProviderLinux();
+  }
+
+  @override
+  Future<String?> getTemporaryPath() {
+    final String environmentTmpDir = _environment['TMPDIR'] ?? '';
+    return Future<String?>.value(
+      environmentTmpDir.isEmpty ? '/tmp' : environmentTmpDir,
+    );
+  }
+
+  @override
+  Future<String?> getApplicationSupportPath() async {
+    final Directory directory =
+        Directory(path.join(xdg.dataHome.path, await _getId()));
+    if (directory.existsSync()) {
+      return directory.path;
+    }
+
+    // This plugin originally used the executable name as a directory.
+    // Use that if it exists for backwards compatibility.
+    final Directory legacyDirectory =
+        Directory(path.join(xdg.dataHome.path, await _getExecutableName()));
+    if (legacyDirectory.existsSync()) {
+      return legacyDirectory.path;
+    }
+
+    // Create the directory, because mobile implementations assume the directory exists.
+    await directory.create(recursive: true);
+    return directory.path;
+  }
+
+  @override
+  Future<String?> getApplicationDocumentsPath() {
+    return Future<String?>.value(xdg.getUserDirectory('DOCUMENTS')?.path);
+  }
+
+  @override
+  Future<String?> getDownloadsPath() {
+    return Future<String?>.value(xdg.getUserDirectory('DOWNLOAD')?.path);
+  }
+
+  // Gets the name of this executable.
+  Future<String> _getExecutableName() async {
+    _executableName ??= path.basenameWithoutExtension(
+        await File('/proc/self/exe').resolveSymbolicLinks());
+    return _executableName!;
+  }
+
+  // Gets the unique ID for this application.
+  Future<String> _getId() async {
+    _applicationId ??= getApplicationId();
+    // If no application ID then fall back to using the executable name.
+    return _applicationId ?? await _getExecutableName();
+  }
+}
diff --git a/packages/path_provider/path_provider_linux/pubspec.yaml b/packages/path_provider/path_provider_linux/pubspec.yaml
index 67638cd..25d1af9 100644
--- a/packages/path_provider/path_provider_linux/pubspec.yaml
+++ b/packages/path_provider/path_provider_linux/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Linux implementation of the path_provider plugin
 repository: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_linux
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
-version: 2.1.2
+version: 2.1.3
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
@@ -16,6 +16,7 @@
         dartPluginClass: PathProviderLinux
 
 dependencies:
+  ffi: ^1.1.2
   flutter:
     sdk: flutter
   path: ^1.8.0
diff --git a/packages/path_provider/path_provider_linux/test/path_provider_linux_test.dart b/packages/path_provider/path_provider_linux/test/path_provider_linux_test.dart
index 6dd3500..1f567c0 100644
--- a/packages/path_provider/path_provider_linux/test/path_provider_linux_test.dart
+++ b/packages/path_provider/path_provider_linux/test/path_provider_linux_test.dart
@@ -4,6 +4,7 @@
 import 'package:flutter_test/flutter_test.dart';
 import 'package:path_provider_linux/path_provider_linux.dart';
 import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
+import 'package:xdg_directories/xdg_directories.dart' as xdg;
 
 void main() {
   TestWidgetsFlutterBinding.ensureInitialized();
@@ -35,8 +36,20 @@
   });
 
   test('getApplicationSupportPath', () async {
-    final PathProviderPlatform plugin = PathProviderPlatform.instance;
-    expect(await plugin.getApplicationSupportPath(), startsWith('/'));
+    final PathProviderPlatform plugin = PathProviderLinux.private(
+        executableName: 'path_provider_linux_test_binary',
+        applicationId: 'com.example.Test');
+    // Note this will fail if ${xdg.dataHome.path}/path_provider_linux_test_binary exists on the local filesystem.
+    expect(await plugin.getApplicationSupportPath(),
+        '${xdg.dataHome.path}/com.example.Test');
+  });
+
+  test('getApplicationSupportPath uses executable name if no application Id',
+      () async {
+    final PathProviderPlatform plugin = PathProviderLinux.private(
+        executableName: 'path_provider_linux_test_binary');
+    expect(await plugin.getApplicationSupportPath(),
+        '${xdg.dataHome.path}/path_provider_linux_test_binary');
   });
 
   test('getApplicationDocumentsPath', () async {