-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TIMOB-24510] Android: Support for custom quick settings tiles. #9310
Changes from 2 commits
e32cffd
63b6d44
adc20e4
4458c41
841a992
4e60faf
46dde07
92d67a5
dab4c15
cd739ef
320e0f9
e3a018a
ff8fd53
bd004e2
471dd9d
c8674b8
39da0e0
cee7856
fd4316d
1d4ae40
58b5c2b
7047aba
c665af6
20cf427
0cee779
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3176,12 +3176,16 @@ AndroidBuilder.prototype.generateJavaFiles = function generateJavaFiles(next) { | |
if (android && android.services) { | ||
var serviceTemplate = fs.readFileSync(path.join(this.templatesDir, 'JSService.java')).toString(), | ||
intervalServiceTemplate = fs.readFileSync(path.join(this.templatesDir, 'JSIntervalService.java')).toString(); | ||
quickSettingsServiceTemplate = fs.readFileSync(path.join(this.templatesDir, 'JSQuickSettingsService.java')).toString(); | ||
Object.keys(android.services).forEach(function (name) { | ||
var service = android.services[name], | ||
tpl = serviceTemplate; | ||
if (service.type == 'interval') { | ||
tpl = intervalServiceTemplate; | ||
this.logger.debug(__('Generating interval service class: %s', service.classname.cyan)); | ||
} else if (service.type == 'quicksettings') { | ||
tpl = quickSettingsServiceTemplate; | ||
this.logger.debug(__('Generating quick settings service class: %s', service.classname.cyan)); | ||
} else { | ||
this.logger.debug(__('Generating service class: %s', service.classname.cyan)); | ||
} | ||
|
@@ -3351,6 +3355,28 @@ AndroidBuilder.prototype.generateTheme = function generateTheme(next) { | |
next(); | ||
}; | ||
|
||
var serviceParser = function(serviceNode, result) { | ||
//add service attributes | ||
appc.xml.forEachAttr(serviceNode, function (attr) { | ||
result[attr.localName] = attr.value; | ||
}); | ||
appc.xml.forEachElement(serviceNode, function (node) { | ||
if (!result[node.tagName]) { | ||
result[node.tagName] = []; | ||
} | ||
//create intent-filter instance | ||
var intentFilter = {}; | ||
var action = []; | ||
intentFilter['action'] = action; | ||
//add atrributes from parent | ||
appc.xml.forEachElement(node, function(intentFilterAaction) { | ||
intentFilter['action'].push(appc.xml.getAttr(intentFilterAaction,'android:name')); | ||
}); | ||
//add intent filter object to array | ||
result[node.tagName].push(intentFilter); | ||
}); | ||
} | ||
|
||
AndroidBuilder.prototype.generateAndroidManifest = function generateAndroidManifest(next) { | ||
if (!this.forceRebuild && fs.existsSync(this.androidManifestFile)) { | ||
return next(); | ||
|
@@ -3568,14 +3594,35 @@ AndroidBuilder.prototype.generateAndroidManifest = function generateAndroidManif | |
tiappServices && Object.keys(tiappServices).forEach(function (filename) { | ||
var service = tiappServices[filename]; | ||
if (service.url) { | ||
var s = { | ||
'name': this.appid + '.' + service.classname | ||
}; | ||
Object.keys(service).forEach(function (key) { | ||
if (!/^(type|name|url|options|classname|android\:name)$/.test(key)) { | ||
s[key.replace(/^android\:/, '')] = service[key]; | ||
var s; | ||
if (service.type == 'quicksettings') { | ||
s = {}; | ||
var serviceName = this.appid + '.' + service.classname; | ||
var icon = '@drawable/' + this.tiapp.icon.replace(/((\.9)?\.(png|jpg))$/, ''); | ||
if (service.icon) { | ||
icon = '@drawable/' + service.icon.replace(/((\.9)?\.(png|jpg))$/, ''); | ||
} | ||
}); | ||
var label = this.tiapp.name; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. var label = service.label || this.tiapp.name; |
||
if (service.label) { | ||
label = service.label; | ||
} | ||
serviceXML = ejs.render(fs.readFileSync(path.join(this.templatesDir, 'QuickService.xml')).toString(), { | ||
serviceName: serviceName, | ||
icon: icon, | ||
label: label | ||
}); | ||
doc = new DOMParser().parseFromString(serviceXML,"text/xml"); | ||
serviceParser(doc.firstChild,s); | ||
} else { | ||
s = { | ||
'name': this.appid + '.' + service.classname | ||
}; | ||
Object.keys(service).forEach(function (key) { | ||
if (!/^(type|name|url|options|classname|android\:name)$/.test(key)) { | ||
s[key.replace(/^android\:/, '')] = service[key]; | ||
} | ||
}); | ||
} | ||
finalAndroidManifest.application.service || (finalAndroidManifest.application.service = {}); | ||
finalAndroidManifest.application.service[s.name] = s; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
package ti.modules.titanium.android.quicksettings; | ||
|
||
import android.annotation.TargetApi; | ||
import android.app.AlertDialog; | ||
import android.app.Dialog; | ||
import android.content.DialogInterface; | ||
import android.graphics.drawable.Icon; | ||
import android.service.quicksettings.TileService; | ||
|
||
import org.appcelerator.kroll.KrollDict; | ||
import org.appcelerator.kroll.KrollRuntime; | ||
import org.appcelerator.kroll.annotations.Kroll; | ||
import org.appcelerator.kroll.common.Log; | ||
import org.appcelerator.titanium.TiApplication; | ||
import org.appcelerator.titanium.TiC; | ||
import org.appcelerator.titanium.proxy.IntentProxy; | ||
import org.appcelerator.titanium.proxy.ServiceProxy; | ||
import org.appcelerator.titanium.view.TiDrawableReference; | ||
|
||
@TargetApi(24) | ||
@Kroll.proxy | ||
public class QuickSettingsServiceProxy extends ServiceProxy { | ||
|
||
private static final String TAG = "QuickSettingsService"; | ||
|
||
private TileService tileService; | ||
//workaround for dealing with Icon class | ||
private Object pathObject = null; | ||
private AlertDialog.Builder builder; | ||
|
||
public QuickSettingsServiceProxy(TileService serviceInstance) { | ||
tileService = serviceInstance; | ||
} | ||
|
||
//Update the tile with the latest changes | ||
@Kroll.method | ||
public void updateTile() { | ||
tileService.getQsTile().updateTile(); | ||
} | ||
|
||
//Setting Tile's icon | ||
@Kroll.method | ||
public void setIcon(Object path) { | ||
tileService.getQsTile().setIcon(Icon.createWithBitmap(TiDrawableReference.fromObject(TiApplication.getAppRootOrCurrentActivity(),path).getBitmap())); | ||
pathObject = path; | ||
} | ||
|
||
//Setting Tile's state | ||
@Kroll.method | ||
public void setState(int state) { | ||
tileService.getQsTile().setState(state); | ||
} | ||
|
||
//Setting Tile's label | ||
@Kroll.method | ||
public void setLabel(String label) { | ||
tileService.getQsTile().setLabel(label); | ||
} | ||
|
||
//Getting Tile'c icon | ||
@Kroll.method | ||
public Object getIcon() { | ||
return pathObject; | ||
} | ||
|
||
//Getting Tile's state | ||
@Kroll.method | ||
public int getState() { | ||
return tileService.getQsTile().getState(); | ||
} | ||
|
||
//Getting Tile's label | ||
@Kroll.method | ||
public String getLabel() { | ||
return tileService.getQsTile().getLabel().toString(); | ||
} | ||
|
||
//Checks if the lock screen is showing. | ||
@Kroll.method | ||
public final boolean isLocked() { | ||
return tileService.isLocked(); | ||
} | ||
|
||
//Checks if the device is in a secure state. | ||
@Kroll.method | ||
public final boolean isSecure() { | ||
return tileService.isSecure(); | ||
} | ||
|
||
//Used to show a dialog. | ||
@Kroll.method | ||
public void showDialog(KrollDict krollDictionary) { | ||
tileService.showDialog(createDialogFromDictionary(krollDictionary)); | ||
} | ||
|
||
//Start an activity while collapsing the panel. | ||
@Kroll.method | ||
public void startActivityAndCollapse(IntentProxy intent) { | ||
tileService.startActivityAndCollapse(intent.getIntent()); | ||
} | ||
|
||
//Prompts the user to unlock the device before executing the JS file. | ||
@Kroll.method | ||
final void unlockAndRun(final String jsToEvaluate) { | ||
tileService.unlockAndRun(new Runnable() { | ||
@Override | ||
public void run() { | ||
KrollRuntime.getInstance().evalString(jsToEvaluate); | ||
} | ||
}); | ||
} | ||
|
||
private Dialog createDialogFromDictionary(KrollDict krollDict) { | ||
builder = new AlertDialog.Builder(tileService.getApplicationContext()); | ||
String[] buttonText = null; | ||
if (krollDict.containsKey(TiC.PROPERTY_TITLE)) { | ||
builder.setTitle(krollDict.getString(TiC.PROPERTY_TITLE)); | ||
} | ||
if (krollDict.containsKey(TiC.PROPERTY_MESSAGE)) { | ||
builder.setMessage(krollDict.getString(TiC.PROPERTY_MESSAGE)); | ||
} | ||
if (krollDict.containsKey(TiC.PROPERTY_BUTTON_NAMES)) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fix |
||
buttonText = krollDict.getStringArray(TiC.PROPERTY_BUTTON_NAMES); | ||
} else if (krollDict.containsKey(TiC.PROPERTY_OK)) { | ||
buttonText = new String[]{krollDict.getString(TiC.PROPERTY_OK)}; | ||
} | ||
if (krollDict.containsKey(TiC.PROPERTY_OPTIONS)) { | ||
String[] optionText = krollDict.getStringArray(TiC.PROPERTY_OPTIONS); | ||
int selectedIndex = krollDict.containsKey(TiC.PROPERTY_SELECTED_INDEX) ? krollDict.getInt(TiC.PROPERTY_SELECTED_INDEX) : -1; | ||
if(selectedIndex >= optionText.length){ | ||
Log.d(TAG, "Ooops invalid selected index specified: " + selectedIndex, Log.DEBUG_MODE); | ||
selectedIndex = -1; | ||
} | ||
|
||
processOptions(optionText, selectedIndex); | ||
} | ||
|
||
if (buttonText != null) { | ||
processButtons(buttonText); | ||
} | ||
return builder.create(); | ||
} | ||
|
||
private void processOptions(String[] optionText,int selectedIndex) | ||
{ | ||
builder.setSingleChoiceItems(optionText, selectedIndex , new DialogInterface.OnClickListener() { | ||
public void onClick(DialogInterface dialog, int which) { | ||
KrollDict eventDictionary = new KrollDict(); | ||
eventDictionary.put(TiC.PROPERTY_ITEM_INDEX,which); | ||
fireEvent(TiC.EVENT_TILE_DIALOG_OPTION_SELECTED, eventDictionary); | ||
} | ||
}); | ||
} | ||
|
||
private void processButtons(String[] buttonText) | ||
{ | ||
builder.setPositiveButton(null, null); | ||
builder.setNegativeButton(null, null); | ||
builder.setNeutralButton(null, null); | ||
builder.setOnCancelListener(new DialogInterface.OnCancelListener() { | ||
@Override | ||
public void onCancel(DialogInterface dialog) | ||
{ | ||
dialog.dismiss(); | ||
fireEvent(TiC.EVENT_TILE_DIALOG_CANCELED,null); | ||
} | ||
}); | ||
|
||
for (int id = 0; id < buttonText.length; id++) { | ||
String text = buttonText[id]; | ||
|
||
switch (id) { | ||
case 0: | ||
builder.setPositiveButton(text, new DialogInterface.OnClickListener() { | ||
@Override | ||
public void onClick(DialogInterface dialog, int which) { | ||
fireEvent(TiC.EVENT_TILE_DIALOG_POSITIVE,null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. spacing of same below |
||
} | ||
}); | ||
break; | ||
case 1: | ||
builder.setNeutralButton(text, new DialogInterface.OnClickListener() { | ||
@Override | ||
public void onClick(DialogInterface dialog, int which) { | ||
fireEvent(TiC.EVENT_TILE_DIALOG_NEUTRAL,null); | ||
} | ||
}); | ||
break; | ||
case 2: | ||
builder.setNegativeButton(text, new DialogInterface.OnClickListener() { | ||
@Override | ||
public void onClick(DialogInterface dialog, int which) { | ||
fireEvent(TiC.EVENT_TILE_DIALOG_NEGATIVE,null); | ||
} | ||
}); | ||
break; | ||
default: | ||
Log.e(TAG, "Only 3 buttons are supported"); | ||
} | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package ti.modules.titanium.android.quicksettings; | ||
|
||
import android.os.Build; | ||
import android.service.quicksettings.TileService; | ||
import android.support.annotation.RequiresApi; | ||
import org.appcelerator.kroll.KrollRuntime; | ||
import org.appcelerator.kroll.util.KrollAssetHelper; | ||
import org.appcelerator.titanium.TiC; | ||
import org.appcelerator.titanium.proxy.ServiceProxy; | ||
|
||
@RequiresApi(api = Build.VERSION_CODES.N) | ||
public class TiJSQuickSettingsService extends TileService{ | ||
|
||
private final ServiceProxy proxy; | ||
|
||
public TiJSQuickSettingsService(String url) { | ||
//create a proxy for this service | ||
proxy = new QuickSettingsServiceProxy(this); | ||
//get the source to be run | ||
String source = KrollAssetHelper.readAsset(url); | ||
//run the module | ||
KrollRuntime.getInstance().runModule(source, url, proxy) ; | ||
} | ||
|
||
//Called when the user clicks on this tile. | ||
@Override | ||
public void onClick() { | ||
proxy.fireEvent(TiC.EVENT_CLICK, null); | ||
} | ||
|
||
//Called when the user adds this tile to Quick Settings. | ||
@Override | ||
public void onTileAdded() { | ||
proxy.fireEvent(TiC.EVENT_TILE_ADDED, null); | ||
} | ||
|
||
//Called when the user removes this tile from Quick Settings. | ||
@Override | ||
public void onTileRemoved() { | ||
proxy.fireEvent(TiC.EVENT_TILE_REMOVED, null); | ||
} | ||
|
||
//Called by the system to notify a Service that it is no longer used and is being removed. | ||
@Override | ||
public void onDestroy() { | ||
proxy.fireEvent(TiC.EVENT_DESTROY, null); | ||
} | ||
|
||
//Called when this tile moves into a listening state. | ||
@Override | ||
public void onStartListening() { | ||
proxy.fireEvent(TiC.EVENT_START_LISTENING, null); | ||
} | ||
|
||
//Called when this tile moves out of the listening state. | ||
@Override | ||
public void onStopListening() { | ||
proxy.fireEvent(TiC.EVENT_STOP_LISTENING, null); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package <%- appid %>; | ||
|
||
import ti.modules.titanium.android.quicksettings.TiJSQuickSettingsService; | ||
|
||
public final class <%- service.classname %> extends TiJSQuickSettingsService { | ||
public <%- service.classname %>() { | ||
super("<%- service.url %>"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<service android:name="<%- serviceName %>" android:label="<%- label %>" android:icon="<%- icon %>" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> | ||
<intent-filter> | ||
<action android:name="android.service.quicksettings.action.QS_TILE"/> | ||
</intent-filter> | ||
</service> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.