@capacitor/android 2.2.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/capacitor/build.gradle +4 -1
  2. package/capacitor/src/main/AndroidManifest.xml +10 -0
  3. package/capacitor/src/main/java/com/getcapacitor/Bridge.java +5 -0
  4. package/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java +0 -2
  5. package/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java +121 -155
  6. package/capacitor/src/main/java/com/getcapacitor/Splash.java +14 -0
  7. package/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java +2 -2
  8. package/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java +10 -3
  9. package/capacitor/src/main/java/com/getcapacitor/plugin/Device.java +22 -0
  10. package/capacitor/src/main/java/com/getcapacitor/plugin/Filesystem.java +8 -9
  11. package/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java +1 -1
  12. package/capacitor/src/main/java/com/getcapacitor/plugin/Share.java +26 -16
  13. package/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java +9 -0
  14. package/capacitor/src/main/java/com/getcapacitor/plugin/camera/ExifWrapper.java +4 -4
  15. package/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java +55 -2
  16. package/capacitor/src/main/java/com/getcapacitor/plugin/notification/DateMatch.java +22 -20
  17. package/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java +48 -16
  18. package/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java +16 -7
  19. package/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java +60 -0
  20. package/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java +1 -1
  21. package/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java +43 -3
  22. package/capacitor/src/main/java/com/getcapacitor/plugin/notification/TimedNotificationPublisher.java +5 -2
  23. package/package.json +2 -2
@@ -4,6 +4,7 @@ ext {
4
4
  androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.1.0-rc02'
5
5
  androidxBrowserVersion = project.hasProperty('androidxBrowserVersion') ? rootProject.ext.androidxBrowserVersion : '1.2.0'
6
6
  androidxLocalbroadcastmanagerVersion = project.hasProperty('androidxLocalbroadcastmanagerVersion') ? rootProject.ext.androidxLocalbroadcastmanagerVersion : '1.0.0'
7
+ androidxExifInterfaceVersion = project.hasProperty('androidxExifInterfaceVersion') ? rootProject.ext.androidxExifInterfaceVersion : '1.2.0'
7
8
  firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? rootProject.ext.firebaseMessagingVersion : '20.1.2'
8
9
  playServicesLocationVersion = project.hasProperty('playServicesLocationVersion') ? rootProject.ext.playServicesLocationVersion : '17.0.0'
9
10
  junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.12'
@@ -64,6 +65,7 @@ dependencies {
64
65
  implementation "com.google.android.material:material:$androidxMaterialVersion"
65
66
  implementation "androidx.browser:browser:$androidxBrowserVersion"
66
67
  implementation "androidx.localbroadcastmanager:localbroadcastmanager:$androidxLocalbroadcastmanagerVersion"
68
+ implementation "androidx.exifinterface:exifinterface:$androidxExifInterfaceVersion"
67
69
  implementation "com.google.firebase:firebase-messaging:$firebaseMessagingVersion"
68
70
  implementation "com.google.android.gms:play-services-location:$playServicesLocationVersion"
69
71
  testImplementation "junit:junit:$junitVersion"
@@ -71,5 +73,6 @@ dependencies {
71
73
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
72
74
  implementation "org.apache.cordova:framework:$cordovaAndroidVersion"
73
75
  testImplementation 'org.json:json:20140107'
74
- testImplementation 'org.mockito:mockito-inline:2.13.0'
76
+ testImplementation 'org.mockito:mockito-inline:2.25.1'
75
77
  }
78
+
@@ -14,5 +14,15 @@
14
14
  <action android:name="com.google.firebase.MESSAGING_EVENT" />
15
15
  </intent-filter>
16
16
  </service>
17
+ <receiver android:name="com.getcapacitor.plugin.notification.LocalNotificationRestoreReceiver" android:directBootAware="true" android:exported="false">
18
+ <intent-filter>
19
+ <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
20
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
21
+ <action android:name="android.intent.action.QUICKBOOT_POWERON" />
22
+ </intent-filter>
23
+ </receiver>
17
24
  </application>
25
+
26
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
27
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
18
28
  </manifest>
@@ -152,6 +152,11 @@ public class Bridge {
152
152
  this.config = new CapConfig(getActivity().getAssets(), config);
153
153
  Logger.init(this.config);
154
154
 
155
+ // Display splash screen if configured
156
+ if (context instanceof BridgeActivity) {
157
+ Splash.showOnLaunch((BridgeActivity) context, this.config);
158
+ }
159
+
155
160
  // Initialize web view and message handler for it
156
161
  this.initWebView();
157
162
  this.msgHandler = new MessageHandler(this, webView, pluginManager);
@@ -82,8 +82,6 @@ public class BridgeActivity extends AppCompatActivity {
82
82
  cordovaInterface.onCordovaInit(pluginManager);
83
83
  bridge = new Bridge(this, webView, initialPlugins, cordovaInterface, pluginManager, preferences, this.config);
84
84
 
85
- Splash.showOnLaunch(this, bridge.getConfig());
86
-
87
85
  if (savedInstanceState != null) {
88
86
  bridge.restoreInstanceState(savedInstanceState);
89
87
  }
@@ -3,195 +3,161 @@ package com.getcapacitor;
3
3
  import android.app.Activity;
4
4
  import android.content.Context;
5
5
  import android.content.res.TypedArray;
6
- import android.net.Uri;
7
6
  import android.os.Bundle;
8
7
  import android.util.AttributeSet;
9
8
  import android.view.LayoutInflater;
10
9
  import android.view.View;
11
10
  import android.view.ViewGroup;
12
11
  import android.webkit.WebView;
13
-
14
12
  import androidx.fragment.app.Fragment;
15
-
16
13
  import com.getcapacitor.android.R;
17
14
  import com.getcapacitor.cordova.MockCordovaInterfaceImpl;
18
15
  import com.getcapacitor.cordova.MockCordovaWebViewImpl;
19
-
16
+ import java.util.ArrayList;
17
+ import java.util.List;
20
18
  import org.apache.cordova.ConfigXmlParser;
21
19
  import org.apache.cordova.CordovaPreferences;
22
20
  import org.apache.cordova.PluginEntry;
23
21
  import org.apache.cordova.PluginManager;
24
22
  import org.json.JSONObject;
25
23
 
26
- import java.util.ArrayList;
27
- import java.util.List;
28
-
29
24
  /**
30
25
  * A simple {@link Fragment} subclass.
31
- * Activities that contain this fragment must implement the
32
- * {@link BridgeFragment.OnFragmentInteractionListener} interface
33
- * to handle interaction events.
34
26
  * Use the {@link BridgeFragment#newInstance} factory method to
35
27
  * create an instance of this fragment.
36
28
  */
37
29
  public class BridgeFragment extends Fragment {
38
- private static final String ARG_START_DIR = "startDir";
39
-
40
- private String startDir;
41
-
42
- private OnFragmentInteractionListener mListener;
43
-
44
- private WebView webView;
45
- protected Bridge bridge;
46
- protected MockCordovaInterfaceImpl cordovaInterface;
47
- protected boolean keepRunning = true;
48
- private ArrayList<PluginEntry> pluginEntries;
49
- private PluginManager pluginManager;
50
- private CordovaPreferences preferences;
51
- private MockCordovaWebViewImpl mockWebView;
52
- private int activityDepth = 0;
53
- private String bridgeStartDir;
54
-
55
- private String lastActivityPlugin;
56
-
57
- private List<Class<? extends Plugin>> initialPlugins = new ArrayList<>();
58
- private JSONObject config = new JSONObject();
59
-
60
- public BridgeFragment() {
61
- // Required empty public constructor
62
- }
63
-
64
- /**
65
- * Use this factory method to create a new instance of
66
- * this fragment using the provided parameters.
67
- *
68
- * @param startDir the directory to serve content from
69
- * @return A new instance of fragment BridgeFragment.
70
- */
71
- public static BridgeFragment newInstance(String startDir) {
72
- BridgeFragment fragment = new BridgeFragment();
73
- Bundle args = new Bundle();
74
- args.putString(ARG_START_DIR, startDir);
75
- fragment.setArguments(args);
76
- return fragment;
77
- }
78
-
79
- protected void init(Bundle savedInstanceState) {
80
- loadConfig(this.getActivity().getApplicationContext(), this.getActivity());
81
- }
82
-
83
- /**
84
- * Load the WebView and create the Bridge
85
- */
86
- protected void load(Bundle savedInstanceState) {
87
- Logger.debug("Starting BridgeActivity");
88
-
89
- Bundle args = getArguments();
90
- String startDir = null;
91
-
92
- if (args != null) {
93
- startDir = getArguments().getString(ARG_START_DIR);
30
+ private static final String ARG_START_DIR = "startDir";
31
+
32
+ private WebView webView;
33
+ protected Bridge bridge;
34
+ protected MockCordovaInterfaceImpl cordovaInterface;
35
+ protected boolean keepRunning = true;
36
+ private ArrayList<PluginEntry> pluginEntries;
37
+ private PluginManager pluginManager;
38
+ private CordovaPreferences preferences;
39
+ private MockCordovaWebViewImpl mockWebView;
40
+
41
+ private List<Class<? extends Plugin>> initialPlugins = new ArrayList<>();
42
+ private JSONObject config = new JSONObject();
43
+
44
+ public BridgeFragment() {
45
+ // Required empty public constructor
46
+ }
47
+
48
+ /**
49
+ * Use this factory method to create a new instance of
50
+ * this fragment using the provided parameters.
51
+ *
52
+ * @param startDir the directory to serve content from
53
+ * @return A new instance of fragment BridgeFragment.
54
+ */
55
+ public static BridgeFragment newInstance(String startDir) {
56
+ BridgeFragment fragment = new BridgeFragment();
57
+ Bundle args = new Bundle();
58
+ args.putString(ARG_START_DIR, startDir);
59
+ fragment.setArguments(args);
60
+ return fragment;
94
61
  }
95
62
 
96
- webView = getView().findViewById(R.id.webview);
97
- cordovaInterface = new MockCordovaInterfaceImpl(this.getActivity());
98
- if (savedInstanceState != null) {
99
- cordovaInterface.restoreInstanceState(savedInstanceState);
63
+ protected void init(Bundle savedInstanceState) {
64
+ loadConfig(this.getActivity().getApplicationContext(), this.getActivity());
100
65
  }
101
66
 
102
- mockWebView = new MockCordovaWebViewImpl(getActivity().getApplicationContext());
103
- mockWebView.init(cordovaInterface, pluginEntries, preferences, webView);
67
+ public void addPlugin(Class<? extends Plugin> plugin) {
68
+ this.initialPlugins.add(plugin);
69
+ }
70
+
71
+ /**
72
+ * Load the WebView and create the Bridge
73
+ */
74
+ protected void load(Bundle savedInstanceState) {
75
+ Logger.debug("Starting BridgeActivity");
76
+
77
+ Bundle args = getArguments();
78
+ String startDir = null;
104
79
 
105
- pluginManager = mockWebView.getPluginManager();
106
- cordovaInterface.onCordovaInit(pluginManager);
80
+ if (args != null) {
81
+ startDir = getArguments().getString(ARG_START_DIR);
82
+ }
107
83
 
108
- if (preferences == null) {
109
- preferences = new CordovaPreferences();
84
+ webView = getView().findViewById(R.id.webview);
85
+ cordovaInterface = new MockCordovaInterfaceImpl(this.getActivity());
86
+ if (savedInstanceState != null) {
87
+ cordovaInterface.restoreInstanceState(savedInstanceState);
88
+ }
89
+
90
+ mockWebView = new MockCordovaWebViewImpl(getActivity().getApplicationContext());
91
+ mockWebView.init(cordovaInterface, pluginEntries, preferences, webView);
92
+
93
+ pluginManager = mockWebView.getPluginManager();
94
+ cordovaInterface.onCordovaInit(pluginManager);
95
+
96
+ if (preferences == null) {
97
+ preferences = new CordovaPreferences();
98
+ }
99
+
100
+ bridge = new Bridge(this.getActivity(), webView, initialPlugins, cordovaInterface, pluginManager, preferences, config);
101
+
102
+ if (startDir != null) {
103
+ bridge.setServerAssetPath(startDir);
104
+ }
105
+
106
+ if (savedInstanceState != null) {
107
+ bridge.restoreInstanceState(savedInstanceState);
108
+ }
109
+ this.keepRunning = preferences.getBoolean("KeepRunning", true);
110
110
  }
111
111
 
112
- bridge = new Bridge(this.getActivity(), webView, initialPlugins, cordovaInterface, pluginManager, preferences, config);
112
+ public void loadConfig(Context context, Activity activity) {
113
+ ConfigXmlParser parser = new ConfigXmlParser();
114
+ parser.parse(context);
115
+ preferences = parser.getPreferences();
116
+ preferences.setPreferencesBundle(activity.getIntent().getExtras());
117
+ pluginEntries = parser.getPluginEntries();
118
+ }
113
119
 
114
- if (startDir != null) {
115
- bridge.setServerAssetPath(startDir);
120
+ @Override
121
+ public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
122
+ super.onInflate(context, attrs, savedInstanceState);
123
+
124
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.bridge_fragment);
125
+ CharSequence c = a.getString(R.styleable.bridge_fragment_start_dir);
126
+
127
+ if (c != null) {
128
+ String startDir = c.toString();
129
+ Bundle args = new Bundle();
130
+ args.putString(ARG_START_DIR, startDir);
131
+ setArguments(args);
132
+ }
133
+ }
134
+
135
+ @Override
136
+ public void onCreate(Bundle savedInstanceState) {
137
+ super.onCreate(savedInstanceState);
116
138
  }
117
139
 
118
- if (savedInstanceState != null) {
119
- bridge.restoreInstanceState(savedInstanceState);
140
+ @Override
141
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
142
+ // Inflate the layout for this fragment
143
+ return inflater.inflate(R.layout.fragment_bridge, container, false);
120
144
  }
121
- this.keepRunning = preferences.getBoolean("KeepRunning", true);
122
- }
123
-
124
- public void loadConfig(Context context, Activity activity) {
125
- ConfigXmlParser parser = new ConfigXmlParser();
126
- parser.parse(context);
127
- preferences = parser.getPreferences();
128
- preferences.setPreferencesBundle(activity.getIntent().getExtras());
129
- pluginEntries = parser.getPluginEntries();
130
- }
131
-
132
-
133
- @Override
134
- public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
135
- super.onInflate(context, attrs, savedInstanceState);
136
-
137
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.bridge_fragment);
138
- CharSequence c = a.getString(R.styleable.bridge_fragment_start_dir);
139
-
140
- if (c != null) {
141
- String startDir = c.toString();
142
- Bundle args = new Bundle();
143
- args.putString(ARG_START_DIR, startDir);
144
- setArguments(args);
145
+
146
+ @Override
147
+ public void onActivityCreated(Bundle savedInstanceState) {
148
+ super.onActivityCreated(savedInstanceState);
149
+ this.init(savedInstanceState);
150
+ this.load(savedInstanceState);
145
151
  }
146
- }
147
-
148
- @Override
149
- public void onCreate(Bundle savedInstanceState) {
150
- super.onCreate(savedInstanceState);
151
- }
152
-
153
- @Override
154
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
155
- Bundle savedInstanceState) {
156
- // Inflate the layout for this fragment
157
- return inflater.inflate(R.layout.fragment_bridge, container, false);
158
- }
159
-
160
- @Override
161
- public void onActivityCreated(Bundle savedInstanceState) {
162
- super.onActivityCreated(savedInstanceState);
163
- this.init(savedInstanceState);
164
- this.load(savedInstanceState);
165
- }
166
-
167
- @Override
168
- public void onAttach(Context context) {
169
- super.onAttach(context);
170
- if (context instanceof OnFragmentInteractionListener) {
171
- mListener = (OnFragmentInteractionListener) context;
172
- } else {
173
- throw new RuntimeException(context.toString()
174
- + " must implement OnFragmentInteractionListener");
152
+
153
+ @Override
154
+ public void onDestroy() {
155
+ super.onDestroy();
156
+ if (this.bridge != null) {
157
+ this.bridge.onDestroy();
158
+ }
159
+ if (this.mockWebView != null) {
160
+ mockWebView.handleDestroy();
161
+ }
175
162
  }
176
- }
177
-
178
- @Override
179
- public void onDetach() {
180
- super.onDetach();
181
- mListener = null;
182
- }
183
-
184
- /**
185
- * This interface must be implemented by activities that contain this
186
- * fragment to allow an interaction in this fragment to be communicated
187
- * to the activity and potentially other fragments contained in that
188
- * activity.
189
- * <p>
190
- * See the Android Training lesson <a href=
191
- * "http://developer.android.com/training/basics/fragments/communicating.html"
192
- * >Communicating with Other Fragments</a> for more information.
193
- */
194
- public interface OnFragmentInteractionListener {
195
- void onFragmentInteraction(Uri uri);
196
- }
197
163
  }
@@ -9,6 +9,7 @@ import android.graphics.Color;
9
9
  import android.graphics.PixelFormat;
10
10
  import android.graphics.drawable.Animatable;
11
11
  import android.graphics.drawable.Drawable;
12
+ import android.graphics.drawable.LayerDrawable;
12
13
  import android.os.Handler;
13
14
  import android.view.Gravity;
14
15
  import android.view.View;
@@ -60,6 +61,18 @@ public class Splash {
60
61
  ((Animatable) splash).start();
61
62
  }
62
63
 
64
+ if(splash instanceof LayerDrawable){
65
+ LayerDrawable layeredSplash = (LayerDrawable) splash;
66
+
67
+ for(int i = 0; i < layeredSplash.getNumberOfLayers(); i++){
68
+ Drawable layerDrawable = layeredSplash.getDrawable(i);
69
+
70
+ if(layerDrawable instanceof Animatable) {
71
+ ((Animatable) layerDrawable).start();
72
+ }
73
+ }
74
+ }
75
+
63
76
  splashImage = new ImageView(c);
64
77
 
65
78
  splashImage.setFitsSystemWindows(true);
@@ -224,6 +237,7 @@ public class Splash {
224
237
  buildViews(a, config);
225
238
 
226
239
  if (isVisible) {
240
+ splashListener.completed();
227
241
  return;
228
242
  }
229
243
 
@@ -356,7 +356,7 @@ public class WebViewLocalServer {
356
356
  private int getStatusCode(InputStream stream, int defaultCode) {
357
357
  int finalStatusCode = defaultCode;
358
358
  try {
359
- if (stream.available() == 0) {
359
+ if (stream.available() == -1) {
360
360
  finalStatusCode = 404;
361
361
  }
362
362
  } catch (IOException e) {
@@ -492,7 +492,7 @@ public class WebViewLocalServer {
492
492
  @Override
493
493
  public int available() throws IOException {
494
494
  InputStream is = getInputStream();
495
- return (is != null) ? is.available() : 0;
495
+ return (is != null) ? is.available() : -1;
496
496
  }
497
497
 
498
498
  @Override
@@ -182,6 +182,7 @@ public class Camera extends Plugin {
182
182
  settings.setHeight(call.getInt("height", 0));
183
183
  settings.setShouldResize(settings.getWidth() > 0 || settings.getHeight() > 0);
184
184
  settings.setShouldCorrectOrientation(call.getBoolean("correctOrientation", CameraSettings.DEFAULT_CORRECT_ORIENTATION));
185
+ settings.setPreserveAspectRatio(call.getBoolean("preserveAspectRatio", false));
185
186
  try {
186
187
  settings.setSource(CameraSource.valueOf(call.getString("source", CameraSource.PROMPT.getSource())));
187
188
  } catch (IllegalArgumentException ex) {
@@ -340,8 +341,9 @@ public class Camera extends Plugin {
340
341
  boolean saveToGallery = call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY);
341
342
  if (saveToGallery && (imageEditedFileSavePath != null || imageFileSavePath != null)) {
342
343
  try {
343
- String fileToSave = imageEditedFileSavePath != null ? imageEditedFileSavePath : imageFileSavePath;
344
- MediaStore.Images.Media.insertImage(getActivity().getContentResolver(), fileToSave, "", "");
344
+ String fileToSavePath = imageEditedFileSavePath != null ? imageEditedFileSavePath : imageFileSavePath;
345
+ File fileToSave = new File(fileToSavePath);
346
+ MediaStore.Images.Media.insertImage(getActivity().getContentResolver(), fileToSavePath, fileToSave.getName(), "");
345
347
  } catch (FileNotFoundException e) {
346
348
  Logger.error(getLogTag(), IMAGE_GALLERY_SAVE_ERROR, e);
347
349
  }
@@ -410,7 +412,12 @@ public class Camera extends Plugin {
410
412
  }
411
413
 
412
414
  if (settings.isShouldResize()) {
413
- final Bitmap newBitmap = ImageUtils.resize(bitmap, settings.getWidth(), settings.getHeight());
415
+ final Bitmap newBitmap = ImageUtils.resize(
416
+ bitmap,
417
+ settings.getWidth(),
418
+ settings.getHeight(),
419
+ settings.getPreserveAspectRatio()
420
+ );
414
421
  bitmap = replaceBitmap(bitmap, newBitmap);
415
422
  }
416
423
  return bitmap;
@@ -3,6 +3,7 @@ package com.getcapacitor.plugin;
3
3
  import android.content.Intent;
4
4
  import android.content.IntentFilter;
5
5
  import android.content.pm.PackageInfo;
6
+ import android.content.pm.ApplicationInfo;
6
7
  import android.os.BatteryManager;
7
8
  import android.os.Environment;
8
9
  import android.os.StatFs;
@@ -32,6 +33,8 @@ public class Device extends Plugin {
32
33
  r.put("osVersion", android.os.Build.VERSION.RELEASE);
33
34
  r.put("appVersion", getAppVersion());
34
35
  r.put("appBuild", getAppBuild());
36
+ r.put("appId", getAppBundleId());
37
+ r.put("appName", getAppName());
35
38
  r.put("platform", getPlatform());
36
39
  r.put("manufacturer", android.os.Build.MANUFACTURER);
37
40
  r.put("uuid", getUuid());
@@ -91,6 +94,25 @@ public class Device extends Plugin {
91
94
  }
92
95
  }
93
96
 
97
+ private String getAppBundleId() {
98
+ try {
99
+ PackageInfo pinfo = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), 0);
100
+ return pinfo.packageName;
101
+ } catch(Exception ex) {
102
+ return "";
103
+ }
104
+ }
105
+
106
+ private String getAppName() {
107
+ try {
108
+ ApplicationInfo applicationInfo = getContext().getApplicationInfo();
109
+ int stringId = applicationInfo.labelRes;
110
+ return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : getContext().getString(stringId);
111
+ } catch(Exception ex) {
112
+ return "";
113
+ }
114
+ }
115
+
94
116
  private String getPlatform() {
95
117
  return "android";
96
118
  }
@@ -579,23 +579,22 @@ public class Filesystem extends Plugin {
579
579
  toObject.delete();
580
580
 
581
581
  assert fromObject != null;
582
- boolean modified = false;
583
582
 
584
583
  if (doRename) {
585
- modified = fromObject.renameTo(toObject);
584
+ boolean modified = fromObject.renameTo(toObject);
585
+ if (!modified) {
586
+ call.error("Unable to rename, unknown reason");
587
+ return;
588
+ }
586
589
  } else {
587
590
  try {
588
591
  copyRecursively(fromObject, toObject);
589
- modified = true;
590
- } catch (IOException ignored) {
592
+ } catch (IOException e) {
593
+ call.error("Unable to perform action: " + e.getLocalizedMessage());
594
+ return;
591
595
  }
592
596
  }
593
597
 
594
- if (!modified) {
595
- call.error("Unable to perform action, unknown reason");
596
- return;
597
- }
598
-
599
598
  call.success();
600
599
  }
601
600
 
@@ -74,7 +74,7 @@ public class LocalNotifications extends Plugin {
74
74
  }
75
75
  JSONArray ids = manager.schedule(call, localNotifications);
76
76
  if (ids != null) {
77
- notificationStorage.appendNotificationIds(localNotifications);
77
+ notificationStorage.appendNotifications(localNotifications);
78
78
  JSObject result = new JSObject();
79
79
  JSArray jsArray = new JSArray();
80
80
  for (int i=0; i < ids.length(); i++) {
@@ -27,27 +27,29 @@ public class Share extends Plugin {
27
27
  call.error("Must provide a URL or Message");
28
28
  return;
29
29
  }
30
+
31
+ if(url != null && !isFileUrl(url) && !isHttpUrl(url)) {
32
+ call.error("Unsupported url");
33
+ return;
34
+ }
35
+
30
36
  Intent intent = new Intent(Intent.ACTION_SEND);
37
+
31
38
  if (text != null) {
32
39
  // If they supplied both fields, concat em
33
- if (url != null && url.startsWith("http")) {
34
- text = text + " " + url;
35
- }
40
+ if (url != null && isHttpUrl(url)) text = text + " " + url;
36
41
  intent.putExtra(Intent.EXTRA_TEXT, text);
37
42
  intent.setTypeAndNormalize("text/plain");
38
- } else if (url != null) {
39
- if (url.startsWith("http")) {
40
- intent.putExtra(Intent.EXTRA_TEXT, url);
41
- intent.setTypeAndNormalize("text/plain");
42
- } else if (url.startsWith("file:")) {
43
- String type = getMimeType(url);
44
- intent.setType(type);
45
- Uri fileUrl = FileProvider.getUriForFile(getActivity(), getContext().getPackageName() + ".fileprovider", new File(Uri.parse(url).getPath()));
46
- intent.putExtra(Intent.EXTRA_STREAM, fileUrl);
47
- } else {
48
- call.error("Unsupported url");
49
- return;
50
- }
43
+ }
44
+
45
+ if(url != null && isHttpUrl(url) && text == null) {
46
+ intent.putExtra(Intent.EXTRA_TEXT, url);
47
+ intent.setTypeAndNormalize("text/plain");
48
+ } else if (url != null && isFileUrl(url)) {
49
+ String type = getMimeType(url);
50
+ intent.setType(type);
51
+ Uri fileUrl = FileProvider.getUriForFile(getActivity(), getContext().getPackageName() + ".fileprovider", new File(Uri.parse(url).getPath()));
52
+ intent.putExtra(Intent.EXTRA_STREAM, fileUrl);
51
53
  }
52
54
 
53
55
  if (title != null) {
@@ -69,4 +71,12 @@ public class Share extends Plugin {
69
71
  }
70
72
  return type;
71
73
  }
74
+
75
+ private boolean isFileUrl(String url) {
76
+ return url.startsWith("file:");
77
+ }
78
+
79
+ private boolean isHttpUrl(String url) {
80
+ return url.startsWith("http");
81
+ }
72
82
  }
@@ -15,6 +15,7 @@ public class CameraSettings {
15
15
  private int width = 0;
16
16
  private int height = 0;
17
17
  private CameraSource source = CameraSource.PROMPT;
18
+ private boolean preserveAspectRatio = false;
18
19
 
19
20
  public CameraResultType getResultType() {
20
21
  return resultType;
@@ -83,4 +84,12 @@ public class CameraSettings {
83
84
  public void setSource(CameraSource source) {
84
85
  this.source = source;
85
86
  }
87
+
88
+ public void setPreserveAspectRatio(boolean preserveAspectRatio) {
89
+ this.preserveAspectRatio = preserveAspectRatio;
90
+ }
91
+
92
+ public boolean getPreserveAspectRatio() {
93
+ return this.preserveAspectRatio;
94
+ }
86
95
  }
@@ -1,8 +1,8 @@
1
1
  package com.getcapacitor.plugin.camera;
2
2
 
3
- import android.media.ExifInterface;
3
+ import androidx.exifinterface.media.ExifInterface;
4
4
 
5
- import static android.media.ExifInterface.*;
5
+ import static androidx.exifinterface.media.ExifInterface.*;
6
6
 
7
7
  import com.getcapacitor.JSObject;
8
8
 
@@ -22,7 +22,7 @@ public class ExifWrapper {
22
22
 
23
23
  // Commented fields are for API 24. Left in to save someone the wrist damage later
24
24
 
25
- p(ret, TAG_APERTURE);
25
+ p(ret, TAG_APERTURE_VALUE);
26
26
  /*
27
27
  p(ret, TAG_ARTIST);
28
28
  p(ret, TAG_BITS_PER_SAMPLE);
@@ -105,7 +105,7 @@ public class ExifWrapper {
105
105
  p(ret, TAG_IMAGE_LENGTH);
106
106
  // p(ret, TAG_IMAGE_UNIQUE_ID);
107
107
  p(ret, TAG_IMAGE_WIDTH);
108
- p(ret, TAG_ISO);
108
+ p(ret, TAG_ISO_SPEED);
109
109
  /*
110
110
  p(ret, TAG_INTEROPERABILITY_INDEX);
111
111
  p(ret, TAG_ISO_SPEED_RATINGS);
@@ -4,7 +4,7 @@ import android.content.Context;
4
4
  import android.database.Cursor;
5
5
  import android.graphics.Bitmap;
6
6
  import android.graphics.Matrix;
7
- import android.media.ExifInterface;
7
+ import androidx.exifinterface.media.ExifInterface;
8
8
  import android.net.Uri;
9
9
  import android.os.Build;
10
10
  import android.provider.MediaStore;
@@ -17,6 +17,32 @@ import java.io.InputStream;
17
17
 
18
18
  public class ImageUtils {
19
19
 
20
+ /**
21
+ * Resize an image to the given width and height.
22
+ * @param bitmap
23
+ * @param width
24
+ * @param height
25
+ * @return a new, scaled Bitmap
26
+ */
27
+ public static Bitmap resize(Bitmap bitmap, final int width, final int height) {
28
+ return ImageUtils.resize(bitmap, width, height, false);
29
+ }
30
+
31
+ /**
32
+ * Resize an image to the given width and height considering the preserveAspectRatio flag.
33
+ * @param bitmap
34
+ * @param width
35
+ * @param height
36
+ * @param preserveAspectRatio
37
+ * @return a new, scaled Bitmap
38
+ */
39
+ public static Bitmap resize(Bitmap bitmap, final int width, final int height, final boolean preserveAspectRatio) {
40
+ if (preserveAspectRatio) {
41
+ return ImageUtils.resizePreservingAspectRatio(bitmap, width, height);
42
+ }
43
+ return ImageUtils.resizeImageWithoutPreservingAspectRatio(bitmap, width, height);
44
+ }
45
+
20
46
  /**
21
47
  * Resize an image to the given width and height. Leave one dimension 0 to
22
48
  * perform an aspect-ratio scale on the provided dimension.
@@ -25,7 +51,7 @@ public class ImageUtils {
25
51
  * @param height
26
52
  * @return a new, scaled Bitmap
27
53
  */
28
- public static Bitmap resize(Bitmap bitmap, final int width, final int height) {
54
+ private static Bitmap resizeImageWithoutPreservingAspectRatio(Bitmap bitmap, final int width, final int height) {
29
55
  float aspect = bitmap.getWidth() / (float) bitmap.getHeight();
30
56
  if (width > 0 && height > 0) {
31
57
  return Bitmap.createScaledBitmap(bitmap, width, height, false);
@@ -38,6 +64,33 @@ public class ImageUtils {
38
64
  return bitmap;
39
65
  }
40
66
 
67
+ /**
68
+ * Resize an image to the given max width and max height. Constraint can be put
69
+ * on one dimension, or both. Resize will always preserve aspect ratio.
70
+ * @param bitmap
71
+ * @param desiredMaxWidth
72
+ * @param desiredMaxHeight
73
+ * @return a new, scaled Bitmap
74
+ */
75
+ private static Bitmap resizePreservingAspectRatio(Bitmap bitmap, final int desiredMaxWidth, final int desiredMaxHeight) {
76
+ int width = bitmap.getWidth();
77
+ int height = bitmap.getHeight();
78
+
79
+ // 0 is treated as 'no restriction'
80
+ int maxHeight = desiredMaxHeight == 0 ? height : desiredMaxHeight;
81
+ int maxWidth = desiredMaxWidth == 0 ? width : desiredMaxWidth;
82
+
83
+ // resize with preserved aspect ratio
84
+ float newWidth = Math.min(width, maxWidth);
85
+ float newHeight = (height * newWidth) / width;
86
+
87
+ if (newHeight > maxHeight) {
88
+ newWidth = (width * maxHeight) / height;
89
+ newHeight = maxHeight;
90
+ }
91
+ return Bitmap.createScaledBitmap(bitmap, Math.round(newWidth), Math.round(newHeight), false);
92
+ }
93
+
41
94
  /**
42
95
  * Transform an image with the given matrix
43
96
  * @param bitmap
@@ -73,6 +73,8 @@ public class DateMatch {
73
73
  private Calendar buildCalendar(Date date) {
74
74
  Calendar cal = Calendar.getInstance();
75
75
  cal.setTime(date);
76
+ cal.set(Calendar.MILLISECOND, 0);
77
+ cal.set(Calendar.SECOND, 0);
76
78
  return cal;
77
79
  }
78
80
 
@@ -92,46 +94,46 @@ public class DateMatch {
92
94
  * Postpone trigger if first schedule matches the past
93
95
  */
94
96
  private long postponeTriggerIfNeeded(Calendar current, Calendar next) {
95
- int currentYear = current.get(Calendar.YEAR);
96
- if (matchesUnit(Calendar.YEAR, current, next)) {
97
- next.set(Calendar.YEAR, currentYear + 1);
98
- this.unit = Calendar.YEAR;
99
- } else if (matchesUnit(Calendar.MONTH, current, next)) {
100
- next.set(Calendar.YEAR, currentYear + 1);
101
- this.unit = Calendar.MONTH;
102
- } else if (matchesUnit(Calendar.DAY_OF_MONTH, current, next)) {
103
- next.set(Calendar.MONTH, current.get(Calendar.MONTH) + 1);
104
- this.unit = Calendar.DAY_OF_MONTH;
105
- } else if (matchesUnit(Calendar.HOUR_OF_DAY, current, next)) {
106
- next.set(Calendar.DAY_OF_MONTH, current.get(Calendar.DAY_OF_MONTH) + 1);
107
- this.unit = Calendar.DAY_OF_MONTH;
108
- } else if (matchesUnit(Calendar.MINUTE, current, next)) {
109
- next.set(Calendar.HOUR_OF_DAY, current.get(Calendar.HOUR_OF_DAY) + 1);
110
- this.unit = Calendar.MINUTE;
97
+ if (next.getTimeInMillis() <= current.getTimeInMillis() && unit != -1) {
98
+ Integer incrementUnit = -1;
99
+ if (unit == Calendar.YEAR || unit == Calendar.MONTH) {
100
+ incrementUnit = Calendar.YEAR;
101
+ } else if (unit == Calendar.DAY_OF_MONTH) {
102
+ incrementUnit = Calendar.MONTH;
103
+ } else if (unit == Calendar.HOUR_OF_DAY) {
104
+ incrementUnit = Calendar.DAY_OF_MONTH;
105
+ } else if (unit == Calendar.MINUTE) {
106
+ incrementUnit = Calendar.HOUR_OF_DAY;
107
+ }
108
+
109
+ if (incrementUnit != -1) {
110
+ next.set(incrementUnit, next.get(incrementUnit) + 1);
111
+ }
111
112
  }
112
113
  return next.getTimeInMillis();
113
114
  }
114
115
 
115
- private boolean matchesUnit(Integer unit, Calendar current, Calendar next) {
116
- return next.get(unit) < current.get(unit);
117
- }
118
-
119
116
  private Calendar buildNextTriggerTime(Date date) {
120
117
  Calendar next = buildCalendar(date);
121
118
  if (year != null) {
122
119
  next.set(Calendar.YEAR, year);
120
+ if (unit == -1) unit = Calendar.YEAR;
123
121
  }
124
122
  if (month != null) {
125
123
  next.set(Calendar.MONTH, month);
124
+ if (unit == -1) unit = Calendar.MONTH;
126
125
  }
127
126
  if (day != null) {
128
127
  next.set(Calendar.DAY_OF_MONTH, day);
128
+ if (unit == -1) unit = Calendar.DAY_OF_MONTH;
129
129
  }
130
130
  if (hour != null) {
131
131
  next.set(Calendar.HOUR_OF_DAY, hour);
132
+ if (unit == -1) unit = Calendar.HOUR_OF_DAY;
132
133
  }
133
134
  if (minute != null) {
134
135
  next.set(Calendar.MINUTE, minute);
136
+ if (unit == -1) unit = Calendar.MINUTE;
135
137
  }
136
138
  return next;
137
139
  }
@@ -33,6 +33,8 @@ public class LocalNotification {
33
33
  private String actionTypeId;
34
34
  private String group;
35
35
  private boolean groupSummary;
36
+ private boolean ongoing;
37
+ private boolean autoCancel;
36
38
  private JSObject extra;
37
39
  private List<LocalNotificationAttachment> attachments;
38
40
  private LocalNotificationSchedule schedule;
@@ -152,6 +154,22 @@ public class LocalNotification {
152
154
  this.groupSummary = groupSummary;
153
155
  }
154
156
 
157
+ public boolean isOngoing() {
158
+ return ongoing;
159
+ }
160
+
161
+ public void setOngoing(boolean ongoing) {
162
+ this.ongoing = ongoing;
163
+ }
164
+
165
+ public boolean isAutoCancel() {
166
+ return autoCancel;
167
+ }
168
+
169
+ public void setAutoCancel(boolean autoCancel) {
170
+ this.autoCancel = autoCancel;
171
+ }
172
+
155
173
  public String getChannelId() {
156
174
  return channelId;
157
175
  }
@@ -186,31 +204,39 @@ public class LocalNotification {
186
204
  call.error("Invalid JSON object sent to NotificationPlugin", e);
187
205
  return null;
188
206
  }
189
- LocalNotification activeLocalNotification = new LocalNotification();
190
- activeLocalNotification.setSource(notification.toString());
191
- activeLocalNotification.setId(notification.getInteger("id"));
192
- activeLocalNotification.setBody(notification.getString("body"));
193
- activeLocalNotification.setActionTypeId(notification.getString("actionTypeId"));
194
- activeLocalNotification.setGroup(notification.getString("group"));
195
- activeLocalNotification.setSound(notification.getString("sound"));
196
- activeLocalNotification.setTitle(notification.getString("title"));
197
- activeLocalNotification.setSmallIcon(notification.getString("smallIcon"));
198
- activeLocalNotification.setIconColor(notification.getString("iconColor"));
199
- activeLocalNotification.setAttachments(LocalNotificationAttachment.getAttachments(notification));
200
- activeLocalNotification.setGroupSummary(notification.getBoolean("groupSummary", false));
201
- activeLocalNotification.setChannelId(notification.getString("channelId"));
207
+
202
208
  try {
203
- activeLocalNotification.setSchedule(new LocalNotificationSchedule(notification));
209
+ LocalNotification activeLocalNotification = buildNotificationFromJSObject(notification);
210
+ resultLocalNotifications.add(activeLocalNotification);
204
211
  } catch (ParseException e) {
205
212
  call.error("Invalid date format sent to Notification plugin", e);
206
213
  return null;
207
214
  }
208
- activeLocalNotification.setExtra(notification.getJSObject("extra"));
209
- resultLocalNotifications.add(activeLocalNotification);
210
215
  }
211
216
  return resultLocalNotifications;
212
217
  }
213
218
 
219
+ public static LocalNotification buildNotificationFromJSObject(JSObject jsonObject) throws ParseException {
220
+ LocalNotification localNotification = new LocalNotification();
221
+ localNotification.setSource(jsonObject.toString());
222
+ localNotification.setId(jsonObject.getInteger("id"));
223
+ localNotification.setBody(jsonObject.getString("body"));
224
+ localNotification.setActionTypeId(jsonObject.getString("actionTypeId"));
225
+ localNotification.setGroup(jsonObject.getString("group"));
226
+ localNotification.setSound(jsonObject.getString("sound"));
227
+ localNotification.setTitle(jsonObject.getString("title"));
228
+ localNotification.setSmallIcon(jsonObject.getString("smallIcon"));
229
+ localNotification.setIconColor(jsonObject.getString("iconColor"));
230
+ localNotification.setAttachments(LocalNotificationAttachment.getAttachments(jsonObject));
231
+ localNotification.setGroupSummary(jsonObject.getBoolean("groupSummary", false));
232
+ localNotification.setChannelId(jsonObject.getString("channelId"));
233
+ localNotification.setSchedule(new LocalNotificationSchedule(jsonObject));
234
+ localNotification.setExtra(jsonObject.getJSObject("extra"));
235
+ localNotification.setOngoing(jsonObject.getBoolean("ongoing", false));
236
+ localNotification.setAutoCancel(jsonObject.getBoolean("autoCancel", true));
237
+
238
+ return localNotification;
239
+ }
214
240
 
215
241
  public static List<Integer> getLocalNotificationPendingList(PluginCall call) {
216
242
  List<JSONObject> notifications = null;
@@ -282,6 +308,8 @@ public class LocalNotification {
282
308
  ", attachments=" + attachments +
283
309
  ", schedule=" + schedule +
284
310
  ", groupSummary=" + groupSummary +
311
+ ", ongoing=" + ongoing +
312
+ ", autoCancel=" + autoCancel +
285
313
  '}';
286
314
  }
287
315
 
@@ -305,6 +333,8 @@ public class LocalNotification {
305
333
  if (attachments != null ? !attachments.equals(that.attachments) : that.attachments != null)
306
334
  return false;
307
335
  if (groupSummary != that.groupSummary) return false;
336
+ if( ongoing != that.ongoing ) return false;
337
+ if( autoCancel != that.autoCancel ) return false;
308
338
  return schedule != null ? schedule.equals(that.schedule) : that.schedule == null;
309
339
  }
310
340
 
@@ -319,6 +349,8 @@ public class LocalNotification {
319
349
  result = 31 * result + (actionTypeId != null ? actionTypeId.hashCode() : 0);
320
350
  result = 31 * result + (group != null ? group.hashCode() : 0);
321
351
  result = 31 * result + Boolean.hashCode(groupSummary);
352
+ result = 31 * result + Boolean.hashCode( ongoing );
353
+ result = 31 * result + Boolean.hashCode( autoCancel );
322
354
  result = 31 * result + (extra != null ? extra.hashCode() : 0);
323
355
  result = 31 * result + (attachments != null ? attachments.hashCode() : 0);
324
356
  result = 31 * result + (schedule != null ? schedule.hashCode() : 0);
@@ -32,6 +32,7 @@ import org.json.JSONArray;
32
32
  import org.json.JSONException;
33
33
  import org.json.JSONObject;
34
34
 
35
+ import java.text.SimpleDateFormat;
35
36
  import java.util.Date;
36
37
  import java.util.List;
37
38
 
@@ -137,13 +138,17 @@ public class LocalNotificationManager {
137
138
 
138
139
  boolean notificationsEnabled = notificationManager.areNotificationsEnabled();
139
140
  if (!notificationsEnabled) {
140
- call.error("Notifications not enabled on this device");
141
+ if(call != null){
142
+ call.error("Notifications not enabled on this device");
143
+ }
141
144
  return null;
142
145
  }
143
146
  for (LocalNotification localNotification : localNotifications) {
144
147
  Integer id = localNotification.getId();
145
148
  if (localNotification.getId() == null) {
146
- call.error("LocalNotification missing identifier");
149
+ if(call != null) {
150
+ call.error("LocalNotification missing identifier");
151
+ }
147
152
  return null;
148
153
  }
149
154
  dismissVisibleNotification(id);
@@ -170,8 +175,8 @@ public class LocalNotificationManager {
170
175
  NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this.context, channelId)
171
176
  .setContentTitle(localNotification.getTitle())
172
177
  .setContentText(localNotification.getBody())
173
- .setAutoCancel(true)
174
- .setOngoing(false)
178
+ .setAutoCancel( localNotification.isAutoCancel( ) )
179
+ .setOngoing( localNotification.isOngoing( ) )
175
180
  .setPriority(NotificationCompat.PRIORITY_DEFAULT)
176
181
  .setGroupSummary(localNotification.isGroupSummary());
177
182
 
@@ -214,7 +219,9 @@ public class LocalNotificationManager {
214
219
  try {
215
220
  mBuilder.setColor(Color.parseColor(iconColor));
216
221
  } catch (IllegalArgumentException ex) {
217
- call.error("Invalid color provided. Must be a hex string (ex: #ff0000");
222
+ if(call != null) {
223
+ call.error("Invalid color provided. Must be a hex string (ex: #ff0000");
224
+ }
218
225
  return;
219
226
  }
220
227
  }
@@ -294,7 +301,6 @@ public class LocalNotificationManager {
294
301
  * on a certain date "shape" (such as every first of the month)
295
302
  */
296
303
  // TODO support different AlarmManager.RTC modes depending on priority
297
- // TODO restore alarm on device shutdown (requires persistence)
298
304
  private void triggerScheduledNotification(Notification notification, LocalNotification request) {
299
305
  AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
300
306
  LocalNotificationSchedule schedule = request.getSchedule();
@@ -333,9 +339,12 @@ public class LocalNotificationManager {
333
339
  // Cron like scheduler
334
340
  DateMatch on = schedule.getOn();
335
341
  if (on != null) {
342
+ long trigger = on.nextTrigger(new Date());
336
343
  notificationIntent.putExtra(TimedNotificationPublisher.CRON_KEY, on.toMatchString());
337
344
  pendingIntent = PendingIntent.getBroadcast(context, request.getId(), notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
338
- alarmManager.setExact(AlarmManager.RTC, on.nextTrigger(new Date()), pendingIntent);
345
+ alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent);
346
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
347
+ Logger.debug(Logger.tags("LN"), "notification " + request.getId() + " will next fire at " + sdf.format(new Date(trigger)));
339
348
  }
340
349
  }
341
350
 
@@ -0,0 +1,60 @@
1
+ package com.getcapacitor.plugin.notification;
2
+
3
+ import android.content.BroadcastReceiver;
4
+ import android.content.Context;
5
+ import android.content.Intent;
6
+ import android.os.UserManager;
7
+
8
+ import com.getcapacitor.CapConfig;
9
+
10
+ import java.util.ArrayList;
11
+ import java.util.Date;
12
+ import java.util.List;
13
+
14
+ import static android.os.Build.VERSION.SDK_INT;
15
+
16
+ public class LocalNotificationRestoreReceiver extends BroadcastReceiver {
17
+
18
+ @Override
19
+ public void onReceive(Context context, Intent intent) {
20
+ if (SDK_INT >= 24) {
21
+ UserManager um = context.getSystemService(UserManager.class);
22
+ if (um == null || !um.isUserUnlocked()) return;
23
+ }
24
+
25
+ NotificationStorage storage = new NotificationStorage(context);
26
+ List<String> ids = storage.getSavedNotificationIds();
27
+
28
+ ArrayList<LocalNotification> notifications = new ArrayList<>(ids.size());
29
+ ArrayList<LocalNotification> updatedNotifications = new ArrayList<>();
30
+ for (String id : ids) {
31
+ LocalNotification notification = storage.getSavedNotification(id);
32
+ if(notification == null) {
33
+ continue;
34
+ }
35
+
36
+ LocalNotificationSchedule schedule = notification.getSchedule();
37
+ if(schedule != null) {
38
+ Date at = schedule.getAt();
39
+ if(at != null && at.before(new Date())) {
40
+ // modify the scheduled date in order to show notifications that would have been delivered while device was off.
41
+ long newDateTime = new Date().getTime() + 15 * 1000;
42
+ schedule.setAt(new Date(newDateTime));
43
+ notification.setSchedule(schedule);
44
+ updatedNotifications.add(notification);
45
+ }
46
+ }
47
+
48
+ notifications.add(notification);
49
+ }
50
+
51
+ if(updatedNotifications.size() > 0){
52
+ storage.appendNotifications(updatedNotifications);
53
+ }
54
+
55
+ CapConfig config = new CapConfig(context.getAssets(), null);
56
+ LocalNotificationManager localNotificationManager = new LocalNotificationManager(storage, null, context, config);
57
+
58
+ localNotificationManager.schedule(null, notifications);
59
+ }
60
+ }
@@ -82,7 +82,7 @@ public class NotificationChannelManager {
82
82
  }
83
83
  AudioAttributes audioAttributes = new AudioAttributes.Builder()
84
84
  .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
85
- .setUsage(AudioAttributes.USAGE_ALARM).build();
85
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION).build();
86
86
  Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/raw/" + sound);
87
87
  notificationChannel.setSound(soundUri, audioAttributes);
88
88
  }
@@ -3,7 +3,14 @@ package com.getcapacitor.plugin.notification;
3
3
  import android.content.Context;
4
4
  import android.content.SharedPreferences;
5
5
 
6
+ import com.getcapacitor.JSObject;
7
+
8
+ import org.json.JSONException;
9
+ import org.json.JSONObject;
10
+
11
+ import java.text.ParseException;
6
12
  import java.util.ArrayList;
13
+ import java.util.Collection;
7
14
  import java.util.Date;
8
15
  import java.util.List;
9
16
  import java.util.Map;
@@ -31,13 +38,12 @@ public class NotificationStorage {
31
38
  /**
32
39
  * Persist the id of currently scheduled notification
33
40
  */
34
- public void appendNotificationIds(List<LocalNotification> localNotifications) {
41
+ public void appendNotifications(List<LocalNotification> localNotifications) {
35
42
  SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID);
36
43
  SharedPreferences.Editor editor = storage.edit();
37
- long creationTime = new Date().getTime();
38
44
  for (LocalNotification request : localNotifications) {
39
45
  String key = request.getId().toString();
40
- editor.putLong(key, creationTime);
46
+ editor.putString(key, request.getSource());
41
47
  }
42
48
  editor.apply();
43
49
  }
@@ -51,6 +57,40 @@ public class NotificationStorage {
51
57
  return new ArrayList<>();
52
58
  }
53
59
 
60
+ public JSObject getSavedNotificationAsJSObject(String key) {
61
+ SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID);
62
+ String notificationString = storage.getString(key, null);
63
+
64
+ if(notificationString == null){
65
+ return null;
66
+ }
67
+
68
+ JSObject jsNotification;
69
+ try {
70
+ jsNotification = new JSObject(notificationString);
71
+ } catch (JSONException ex) {
72
+ return null;
73
+ }
74
+
75
+ return jsNotification;
76
+ }
77
+
78
+ public LocalNotification getSavedNotification(String key) {
79
+ JSObject jsNotification = getSavedNotificationAsJSObject(key);
80
+ if(jsNotification == null) {
81
+ return null;
82
+ }
83
+
84
+ LocalNotification notification;
85
+ try {
86
+ notification = LocalNotification.buildNotificationFromJSObject(jsNotification);
87
+ } catch (ParseException ex) {
88
+ return null;
89
+ }
90
+
91
+ return notification;
92
+ }
93
+
54
94
  /**
55
95
  * Remove the stored notifications
56
96
  */
@@ -9,6 +9,7 @@ import android.content.Context;
9
9
  import android.content.Intent;
10
10
  import com.getcapacitor.Logger;
11
11
 
12
+ import java.text.SimpleDateFormat;
12
13
  import java.util.Date;
13
14
 
14
15
  /**
@@ -42,8 +43,10 @@ public class TimedNotificationPublisher extends BroadcastReceiver {
42
43
  long trigger = date.nextTrigger(new Date());
43
44
  Intent clone = (Intent) intent.clone();
44
45
  PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, clone, PendingIntent.FLAG_CANCEL_CURRENT);
45
- alarmManager.set(AlarmManager.RTC, trigger, pendingIntent);
46
+ alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent);
47
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
48
+ Logger.debug(Logger.tags("LN"), "notification " + id + " will next fire at " + sdf.format(new Date(trigger)));
46
49
  }
47
50
  }
48
-
51
+
49
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capacitor/android",
3
- "version": "2.2.1",
3
+ "version": "2.4.2",
4
4
  "description": "Capacitor: cross-platform mobile apps with the web",
5
5
  "homepage": "https://capacitor.ionicframework.com/",
6
6
  "author": "Ionic Team <hi@ionic.io> (https://ionicframework.com) ",
@@ -16,7 +16,7 @@
16
16
  "access": "public"
17
17
  },
18
18
  "peerDependencies": {
19
- "@capacitor/core": "~2.2.0"
19
+ "@capacitor/core": "~2.4.0"
20
20
  },
21
21
  "gitHead": "844c0feba4a801ee5041429a82d0f805c9e665b0"
22
22
  }