@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.
- package/capacitor/build.gradle +4 -1
- package/capacitor/src/main/AndroidManifest.xml +10 -0
- package/capacitor/src/main/java/com/getcapacitor/Bridge.java +5 -0
- package/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java +0 -2
- package/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java +121 -155
- package/capacitor/src/main/java/com/getcapacitor/Splash.java +14 -0
- package/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java +2 -2
- package/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java +10 -3
- package/capacitor/src/main/java/com/getcapacitor/plugin/Device.java +22 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/Filesystem.java +8 -9
- package/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java +1 -1
- package/capacitor/src/main/java/com/getcapacitor/plugin/Share.java +26 -16
- package/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java +9 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/camera/ExifWrapper.java +4 -4
- package/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java +55 -2
- package/capacitor/src/main/java/com/getcapacitor/plugin/notification/DateMatch.java +22 -20
- package/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java +48 -16
- package/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java +16 -7
- package/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java +60 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java +1 -1
- package/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java +43 -3
- package/capacitor/src/main/java/com/getcapacitor/plugin/notification/TimedNotificationPublisher.java +5 -2
- package/package.json +2 -2
package/capacitor/build.gradle
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
80
|
+
if (args != null) {
|
|
81
|
+
startDir = getArguments().getString(ARG_START_DIR);
|
|
82
|
+
}
|
|
107
83
|
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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() ==
|
|
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() :
|
|
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
|
|
344
|
-
|
|
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(
|
|
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
|
-
|
|
590
|
-
|
|
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.
|
|
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
|
|
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
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
3
|
+
import androidx.exifinterface.media.ExifInterface;
|
|
4
4
|
|
|
5
|
-
import static
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
174
|
-
.setOngoing(
|
|
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
|
|
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,
|
|
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
|
+
}
|
package/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java
CHANGED
|
@@ -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.
|
|
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
|
}
|
package/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
*/
|
package/capacitor/src/main/java/com/getcapacitor/plugin/notification/TimedNotificationPublisher.java
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
19
|
+
"@capacitor/core": "~2.4.0"
|
|
20
20
|
},
|
|
21
21
|
"gitHead": "844c0feba4a801ee5041429a82d0f805c9e665b0"
|
|
22
22
|
}
|