AOSP - install packages in background without user confirmation - android

I am building AOSP for Android version 8.1 and I want to implement my own app store and hence need to install apks onto the system.
The only way I've seen so far is using something like the following:
val intent = Intent(Intent.ACTION_VIEW)
val file = File(Environment.getExternalStorageDirectory().toString() + "/APKs/" + "spotify.apk")
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
However, this requests confirmation before each package is installed from the user. Is there any other way to achieve this without user confirmation if the app is a system app built into the image? Also what is the installation effectively doing in the background? Just moving the APK file to /data/app or what exactly happens?

Take a look runInstall() method
https://android.googlesource.com/platform/frameworks/base/+/master/cmds/pm/src/com/android/commands/pm/Pm.java
Required permission:
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>

I solved it like so:
Prerequisite:
Your APK needs to be signed by system as correctly pointed out earlier. One way to achieve that is building the AOSP image yourself and adding the source code into the build.
Code:
Once installed as a system app, you can use the package manager methods to install and uninstall an APK as following:
Install:
public boolean install(final String apkPath, final Context context) {
Log.d(TAG, "Installing apk at " + apkPath);
try {
final Uri apkUri = Uri.fromFile(new File(apkPath));
final String installerPackageName = "MyInstaller";
context.getPackageManager().installPackage(apkUri, installObserver, PackageManager.INSTALL_REPLACE_EXISTING, installerPackageName);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
Uninstall:
public boolean uninstall(final String packageName, final Context context) {
Log.d(TAG, "Uninstalling package " + packageName);
try {
context.getPackageManager().deletePackage(packageName, deleteObserver, PackageManager.DELETE_ALL_USERS);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
To have a callback once your APK is installed/uninstalled you can use this:
/**
* Callback after a package was installed be it success or failure.
*/
private class InstallObserver implements IPackageInstallObserver {
#Override
public void packageInstalled(String packageName, int returnCode) throws RemoteException {
if (packageName != null) {
Log.d(TAG, "Successfully installed package " + packageName);
callback.onAppInstalled(true, packageName);
} else {
Log.e(TAG, "Failed to install package.");
callback.onAppInstalled(false, null);
}
}
#Override
public IBinder asBinder() {
return null;
}
}
/**
* Callback after a package was deleted be it success or failure.
*/
private class DeleteObserver implements IPackageDeleteObserver {
#Override
public void packageDeleted(String packageName, int returnCode) throws RemoteException {
if (packageName != null) {
Log.d(TAG, "Successfully uninstalled package " + packageName);
callback.onAppUninstalled(true, packageName);
} else {
Log.e(TAG, "Failed to uninstall package.");
callback.onAppUninstalled(false, null);
}
}
#Override
public IBinder asBinder() {
return null;
}
}
/**
* Callback to give the flow back to the calling class.
*/
public interface InstallerCallback {
void onAppInstalled(final boolean success, final String packageName);
void onAppUninstalled(final boolean success, final String packageName);
}

Related

Customizing call back for ActivityResultContracts.TakePicture() to get passed uri as well

The following are the methods i have declared for capturing image from Android 11 version supporting app. I want to cutomize the call back to get the input uri, instead of just getting a boolean status.
As of now the following is methods, which are working fine.
private final ActivityResultLauncher<Uri> takePicture =
registerForActivityResult(new ActivityResultContracts.TakePicture(), this::onTakePictureResult);
private void android11Capture() {
try {
Context ctx = requireContext();
String authorities = ctx.getPackageName() + AUTHORITY_SUFFIX;
Uri imageUri = FileProvider.getUriForFile(ctx, authorities, createImageFile());
takePicture.launch(imageUri);
} catch (IOException e) {
e.printStackTrace();
}
}
public void onTakePictureResult(boolean success) {
showToast(success + "");
if (success) {
cropImage();
} else {
showToast("taking picture failed");
}
}
I just want to customize the method onTakePictureResult. Such that i can get the passed imageUri as well in call back)
public void onTakePictureResult(boolean success, Uri imageUri) {
showToast(success + "");
if (success) {
cropImage(imageUri);
} else {
showToast("taking picture failed");
}
}
Note: Since i don't want to create a separate variable for this. I want to know is this possible ?
Thanks in advance for the help :)

Android root activity sometimes started spontaneously without any user actions

I have a strange issue which I cannot explain. In the manifest file the launch activity of my app is defined as follows:
<activity
android:name="com.xxx.xxx.xxx.StartupActivity"
android:label="#string/app_name"
android:theme="#android:style/Theme.Light.NoTitleBar"
android:screenOrientation="sensorPortrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
In the StartUpActivity the following check is performed:
protected void startIntent() {
Intent intent;
if (checkCurrentProfile()) {
Notifier.showHttpToast(R.string.toastLoggedIn);
//try to update the device token in the database
Request.updateDeviceToken();
intent = new Intent(this, GameListActivity.class);
} else {
intent = new Intent(this, RegisterActivity.class);
}
startActivity(intent);
finish();
}
So if the user has a valid account the GameListActivity is shown as root activity:
<activity
android:name="com.xxx.xxx.xxx.xxx.GameListActivity"
android:label="#string/app_name"
android:theme="#style/MyTheme"
android:screenOrientation="sensorPortrait" >
</activity>
The issue now is the following: sometimes the system brings the root activity to the front spontaneously without any user actions. It only occurs sometimes, but I can't figure out the cause. Can anyone help me out here?
The StartUpActivity looks like this:
public class StartupActivity extends StartupCoreActivity implements OnRegisterGCMListener {
private IabHelper mHelper;
private IabHelper.QueryInventoryFinishedListener mQueryInventoryFinishedListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerOnRegisterGCMListener(this);
if (!registerGCMneeded()) {
initInAppBilling();
}
}
#Override
public void registerGCMfinished() {
initInAppBilling();
}
private void initInAppBilling() {
boolean hasPremium = Prefs.getBoolValue(getResources().getString(R.string.pref_key_upgrade_premium), false);
if (hasPremium) {
//unlocking contents not needed
startIntent();
} else {
Prefs.storeValue(getResources().getString(R.string.pref_key_upgrade_premium), false);
mHelper = new IabHelper(this, C.Billing.BILLING_KEY);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (result.isSuccess()) {
queryInventory();
} else {
startIntent();
}
}
});
}
}
private void queryInventory() {
String[] products = {C.Billing.ITEM_SKU};
mQueryInventoryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inv) {
if (result.isSuccess()) {
checkPremiumVersion(inv);
} else {
startIntent();
}
}
};
mHelper.queryInventoryAsync(true, Arrays.asList(products), mQueryInventoryFinishedListener);
}
private void checkPremiumVersion(Inventory inv) {
if (inv.hasPurchase(C.Billing.ITEM_SKU)) {
Request.updPremiumVersion();
Prefs.storeValue(getResources().getString(R.string.pref_key_upgrade_premium), true);
Notifier.showHttpToast(R.string.toastPremiumContentsUnlocked);
}
startIntent();
}
}
And the StartupCoreActivity looks like this:
public class StartupCoreActivity extends Activity {
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = xxxx;
GoogleCloudMessaging mGcm;
Context mContext;
String mRegId;
/**
* Substitute you own sender ID here. This is the project number you got
* from the API Console, as described in "Getting Started."
*/
String SENDER_ID = "xxxxxxxxxx";
private OnRegisterGCMListener mOnRegisterGCMListener = null;
public void registerOnRegisterGCMListener(OnRegisterGCMListener listener) {
mOnRegisterGCMListener = listener;
}
protected boolean registerGCMneeded() {
mContext = getApplicationContext();
// Check device for Play Services APK.
if (checkPlayServices()) {
// If this check succeeds, proceed with normal processing.
// Otherwise, prompt user to get valid Play Services APK.
mGcm = GoogleCloudMessaging.getInstance(this);
mRegId = getRegistrationId(mContext);
if (mRegId.isEmpty()) {
registerInBackground();
return true;
} else {
// note we never called setContentView()
return false;
}
}
return false;
}
protected void startIntent() {
Intent intent;
if (checkCurrentProfile()) {
Notifier.showHttpToast(R.string.toastLoggedIn);
//try to update the device token in the database
Request.updateDeviceToken();
intent = new Intent(this, GameListActivity.class);
} else {
intent = new Intent(this, RegisterActivity.class);
}
startActivity(intent);
finish();
}
private boolean checkCurrentProfile() {
KUPlayer me = G.getMySelf();
if (me.getPlayerId() <= 0) {
//database was not present yet and has been created
//or database was present, but profile cannot be read anymore
// make sure Login screen appears ONLY if PlayerID cannot be retrieved anymore
if (G.getPlayerID() <=0) {
Prefs.storeValue(Prefs.PREF_KEY_PWD_SAVED, false);
return false;
}
}
return true;
}
// You need to do the Play Services APK check here too.
#Override
protected void onResume() {
super.onResume();
checkPlayServices();
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
finish();
}
return false;
}
return true;
}
/**
* Registers the application with mGcm servers asynchronously.
* <p>
* Stores the registration ID and the app versionCode in the application's
* shared preferences.
*/
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (mGcm == null) {
mGcm = GoogleCloudMessaging.getInstance(mContext);
}
mRegId = mGcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + mRegId;
// Persist the mRegId - no need to register again.
storeRegistrationId(mContext, mRegId);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
// note we never called setContentView()
if (mOnRegisterGCMListener != null) {
mOnRegisterGCMListener.registerGCMfinished();
}
}
}.execute(null, null, null);
}
/**
* #return Application's version code from the {#code PackageManager}.
*/
private static int getAppVersionCode(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
private static String getAppVersionName(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionName;
} catch (NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
/**
* Stores the registration ID and the app versionCode in the application's
* {#code SharedPreferences}.
*
* #param mContext application's mContext.
* #param mRegId registration ID
*/
private void storeRegistrationId(Context context, String regId) {
Prefs.storeValue(Prefs.PREF_KEY_DEVICE_TOKEN, regId);
Prefs.storeValue(Prefs.PREF_KEY_APP_VERSION_CODE, getAppVersionCode(context));
Prefs.storeValue(Prefs.PREF_KEY_APP_VERSION_NAME, getAppVersionName(context));
AppRate.resetAfterUpdate();
}
/**
* Gets the current registration ID for application on mGcm service, if there is one.
* <p>
* If result is empty, the app needs to register.
*
* #return registration ID, or empty string if there is no existing
* registration ID.
*/
private String getRegistrationId(Context context) {
String registrationId = Prefs.getStringValue(Prefs.PREF_KEY_DEVICE_TOKEN);
if (registrationId.isEmpty()) {
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing mRegId is not guaranteed to work with the new
// app version.
int registeredVersion = Prefs.getIntValue(Prefs.PREF_KEY_APP_VERSION_CODE);
int currentVersion = getAppVersionCode(context);
if (registeredVersion != currentVersion) {
return "";
}
return registrationId;
}
}
I guess it has to do with the following known Android bug:
How to prevent multiple instances of an activity when it is launched with different intents
And as suggested put the following code in the onCreate method of the rootActivity:
// Possible work around for market launches. See http://code.google.com/p/android/issues/detail?id=2373
// for more details. Essentially, the market launches the main activity on top of other activities.
// we never want this to happen. Instead, we check if we are the root and if not, we finish.
if (!isTaskRoot()) {
final Intent intent = getIntent();
final String intentAction = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) {
finish();
return;
}
}
I tested it and when you start your app from Google Play store in stead of launch screen, then indeed finish() is called from above code.

How to integrate SIP into Android?

How to implement SIP protocol in Android ?
there is any SDK or library available to implement it easily into Android?
Here is a third party Library with sample code. You can use this, I have used it and it works fine.
Android 2.3 or higher provides API for SIP.
Refer this link for SIP in Android
also you can see DEMO project for SIP from Sample
update:
Android SDK Samples on github.
SipDemo1, SipDemo2
Search for SipDemo project in samples for android 4.0.3 SDK version(API level -15)
I have been investigated this sort of problem for a long time and found out that SipManager and SipProfile are unfortunatelly poor and extremelly buggy.
So I found a Linphone library. There is a link for their wiki. I implemented it in my project using maven:
repositories {
...
maven { "https://linphone.org/maven_repository/"}
}
Also there is a sample of using it on gitlab: link here, it's pretty fresh, for now :)
If the link would crash, I just copy/paste the most important part of how to use linphone's core:
public class LinphoneService extends Service {
private static final String START_LINPHONE_LOGS = " ==== Device information dump ====";
// Keep a static reference to the Service so we can access it from anywhere in the app
private static LinphoneService sInstance;
private Handler mHandler;
private Timer mTimer;
private Core mCore;
private CoreListenerStub mCoreListener;
public static boolean isReady() {
return sInstance != null;
}
public static LinphoneService getInstance() {
return sInstance;
}
public static Core getCore() {
return sInstance.mCore;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
// The first call to liblinphone SDK MUST BE to a Factory method
// So let's enable the library debug logs & log collection
String basePath = getFilesDir().getAbsolutePath();
Factory.instance().setLogCollectionPath(basePath);
Factory.instance().enableLogCollection(LogCollectionState.Enabled);
Factory.instance().setDebugMode(true, getString(R.string.app_name));
// Dump some useful information about the device we're running on
Log.i(START_LINPHONE_LOGS);
dumpDeviceInformation();
dumpInstalledLinphoneInformation();
mHandler = new Handler();
// This will be our main Core listener, it will change activities depending on events
mCoreListener = new CoreListenerStub() {
#Override
public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
Toast.makeText(LinphoneService.this, message, Toast.LENGTH_SHORT).show();
if (state == Call.State.IncomingReceived) {
Toast.makeText(LinphoneService.this, "Incoming call received, answering it automatically", Toast.LENGTH_LONG).show();
// For this sample we will automatically answer incoming calls
CallParams params = getCore().createCallParams(call);
params.enableVideo(true);
call.acceptWithParams(params);
} else if (state == Call.State.Connected) {
// This stats means the call has been established, let's start the call activity
Intent intent = new Intent(LinphoneService.this, CallActivity.class);
// As it is the Service that is starting the activity, we have to give this flag
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
};
try {
// Let's copy some RAW resources to the device
// The default config file must only be installed once (the first time)
copyIfNotExist(R.raw.linphonerc_default, basePath + "/.linphonerc");
// The factory config is used to override any other setting, let's copy it each time
copyFromPackage(R.raw.linphonerc_factory, "linphonerc");
} catch (IOException ioe) {
Log.e(ioe);
}
// Create the Core and add our listener
mCore = Factory.instance()
.createCore(basePath + "/.linphonerc", basePath + "/linphonerc", this);
mCore.addListener(mCoreListener);
// Core is ready to be configured
configureCore();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
// If our Service is already running, no need to continue
if (sInstance != null) {
return START_STICKY;
}
// Our Service has been started, we can keep our reference on it
// From now one the Launcher will be able to call onServiceReady()
sInstance = this;
// Core must be started after being created and configured
mCore.start();
// We also MUST call the iterate() method of the Core on a regular basis
TimerTask lTask =
new TimerTask() {
#Override
public void run() {
mHandler.post(
new Runnable() {
#Override
public void run() {
if (mCore != null) {
mCore.iterate();
}
}
});
}
};
mTimer = new Timer("Linphone scheduler");
mTimer.schedule(lTask, 0, 20);
return START_STICKY;
}
#Override
public void onDestroy() {
mCore.removeListener(mCoreListener);
mTimer.cancel();
mCore.stop();
// A stopped Core can be started again
// To ensure resources are freed, we must ensure it will be garbage collected
mCore = null;
// Don't forget to free the singleton as well
sInstance = null;
super.onDestroy();
}
#Override
public void onTaskRemoved(Intent rootIntent) {
// For this sample we will kill the Service at the same time we kill the app
stopSelf();
super.onTaskRemoved(rootIntent);
}
private void configureCore() {
// We will create a directory for user signed certificates if needed
String basePath = getFilesDir().getAbsolutePath();
String userCerts = basePath + "/user-certs";
File f = new File(userCerts);
if (!f.exists()) {
if (!f.mkdir()) {
Log.e(userCerts + " can't be created.");
}
}
mCore.setUserCertificatesPath(userCerts);
}
private void dumpDeviceInformation() {
StringBuilder sb = new StringBuilder();
sb.append("DEVICE=").append(Build.DEVICE).append("\n");
sb.append("MODEL=").append(Build.MODEL).append("\n");
sb.append("MANUFACTURER=").append(Build.MANUFACTURER).append("\n");
sb.append("SDK=").append(Build.VERSION.SDK_INT).append("\n");
sb.append("Supported ABIs=");
for (String abi : Version.getCpuAbis()) {
sb.append(abi).append(", ");
}
sb.append("\n");
Log.i(sb.toString());
}
private void dumpInstalledLinphoneInformation() {
PackageInfo info = null;
try {
info = getPackageManager().getPackageInfo(getPackageName(), 0);
} catch (PackageManager.NameNotFoundException nnfe) {
Log.e(nnfe);
}
if (info != null) {
Log.i(
"[Service] Linphone version is ",
info.versionName + " (" + info.versionCode + ")");
} else {
Log.i("[Service] Linphone version is unknown");
}
}
private void copyIfNotExist(int ressourceId, String target) throws IOException {
File lFileToCopy = new File(target);
if (!lFileToCopy.exists()) {
copyFromPackage(ressourceId, lFileToCopy.getName());
}
}
private void copyFromPackage(int ressourceId, String target) throws IOException {
FileOutputStream lOutputStream = openFileOutput(target, 0);
InputStream lInputStream = getResources().openRawResource(ressourceId);
int readByte;
byte[] buff = new byte[8048];
while ((readByte = lInputStream.read(buff)) != -1) {
lOutputStream.write(buff, 0, readByte);
}
lOutputStream.flush();
lOutputStream.close();
lInputStream.close();
}
}
I hope, that will help somebody, because I spend a lot of time trying to find it!
I used by this library:
https://www.mizu-voip.com/Software/SIPSDK/AndroidSIPSDK.aspx
it is very easy.
also i add button for answer the call:
mysipclient.Accept(mysipclient.GetLine());

Android InAppBilling - what to do when user presses the buy button?

I set up the "Dungeons" InAppBilling example locally and I am ready to try it out, but I am a bit confused. I have a button like this:
Button donate = (Button)findViewById(R.id.donate);
donate.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
// But what do I do here? :)
}
});
And when it is called, what do I need to do to actually go to the pay screen on android store?
Thanks!
I better suggest you to use this code as this example is quiet simple and easy to handle at first .. the things you have to do is
http://blog.blundell-apps.com/simple-inapp-billing-payment/
download the sample project code from the above link (Having Description of code and download link at bottom)
in your android project where you want to implement in app billing, create package com.android.vending.billing and place IMarketBillingService.aidl (you can find this and all files mention below in the project you downloaded in step 1)
place following utility files in any package and correct import statements accordingly.
* BillingHelper.java
* BillingReceiver.java
* BillingSecurity.java
* BillingService.java
* C.java
place the public key (you can find it in developer console in the bottom section of edit profile) in the BillingSecurity.java in line saying String base64EncodedPublicKey = "your public key here"
Declare the following permission (outside the application tag), service and receiver (Inside the application tag) in your manifest like shown below(can also see manifest which is along the code for reference)
//outside the application tag
<uses-permission android:name="com.android.vending.BILLING" />
// Inside the application tag
<service android:name=".BillingService" />
<receiver android:name=".BillingReceiver">
<intent-filter>
<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
<action android:name="com.android.vending.billing.RESPONSE_CODE" />
<action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
</intent-filter>
</receiver>
place the following code as mentioned with there places in your activity where purchase is being held.
//at the starting of your onCreate()
startService(new Intent(mContext, BillingService.class));
BillingHelper.setCompletedHandler(mTransactionHandler);
//outside onCreate() Within class
public Handler mTransactionHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
Log.i(TAG, "Transaction complete");
Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);
if(BillingHelper.latestPurchase.isPurchased()){
//code here which is to be performed after successful purchase
}
};
};
//code to initiate a purchase... can be placed in onClickListener etc
if(BillingHelper.isBillingSupported()){
BillingHelper.requestPurchase(mContext, "android.test.purchased");
// where android.test.purchased is test id for fake purchase, when you create products through developer console you can set a code to pass the id(which is given on developer console while creating a product) of the item which is selected for purchase to intiate purchase of that item.
} else {
Log.i(TAG,"Can't purchase on this device");
// Do Anything Heer to show user that purchase not possible on this device
}
Note: To do a test purchase you need to put the public key in BillingSecurity.java as mentioned above secondly you need to upload the apk to the developer console(you can leave it uupublished and unactive) and thirdly you need a real android device(emulator wouldn't work) having updated play store app.
Note: the account needed for in app purchase and described in all above discussion is not just simple publisher account its publisher account embedded with google merchant wallet account. The details can be found in link below.
http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=113468
1)download
http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-download
2)add
http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-add-aidl
3)add permission in your android manifest file
http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-permission
now your project should look like this...
4) place the public key (you can find it in developer console in the
bottom section of edit profile) in the Security.java in line saying
String base64EncodedPublicKey = "your public key here"
5) and finally your activity which have button should be look like this
public class YourActivity extends Activity implements OnClickListener {
String issueProductId = "Your Product ID";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.updates);
SetInAppBilling();
Button donate = (Button) findViewById(R.id.donate);
donate.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
if (mBillingService.requestPurchase(issueProductId, null)) {
} else {
showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
Log.i("tag", "Can't purchase on this device");
}
}
});
}
public void register() {
ResponseHandler.register(mDungeonsPurchaseObserver);
}
public void unregister() {
ResponseHandler.unregister(mDungeonsPurchaseObserver);
}
public void close_unbind() {
if (mPurchaseDatabase != null)
// mPurchaseDatabase.close();
if (mBillingService != null)
mBillingService.unbind();
// stopService(new Intent(this, BillingService.class));
}
/**
* Called when this activity becomes visible.
*/
#Override
protected void onStart() {
super.onStart();
register();
}
/**
* Called when this activity is no longer visible.
*/
#Override
protected void onStop() {
unregister();
super.onStop();
}
#Override
protected void onDestroy() {
close_unbind();
super.onDestroy();
}
private static final String TAG = "YourActivity";
private static final String DB_INITIALIZED = "db_initialized";
// private static final String Dir_Check = "Dir_Check";
private DungeonsPurchaseObserver mDungeonsPurchaseObserver;
private Handler mHandler;
private BillingService mBillingService;
private PurchaseDatabase mPurchaseDatabase;
private static final int DIALOG_CANNOT_CONNECT_ID = 1;
private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
private Cursor mOwnedItemsCursor;
public void SetInAppBilling() {
mHandler = new Handler();
mDungeonsPurchaseObserver = new DungeonsPurchaseObserver(mHandler);
mBillingService = new BillingService();
mBillingService.setContext(this);
mPurchaseDatabase = new PurchaseDatabase(this);
mOwnedItemsCursor = mPurchaseDatabase
.queryAllPurchasedHistroyTabelItems();
startManagingCursor(mOwnedItemsCursor);
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
// Check if billing is supported.
ResponseHandler.register(mDungeonsPurchaseObserver);
if (!mBillingService.checkBillingSupported()) {
showDialog(DIALOG_CANNOT_CONNECT_ID);
}
}
private class DungeonsPurchaseObserver extends PurchaseObserver {
public DungeonsPurchaseObserver(Handler handler) {
super(YourActiviy.this, handler);
}
#Override
public void onBillingSupported(boolean supported) {
Log.i(TAG, "supportedCheck: " + supported);
if (Consts.DEBUG) {
Log.i(TAG, "supported: " + supported);
}
if (supported) {
restoreDatabase();
} else {
showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
}
}
#Override
public void onPurchaseStateChange(PurchaseState purchaseState,
String itemId, int quantity, long purchaseTime,
String developerPayload) {
if (Consts.DEBUG) {
Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " "
+ purchaseState);
}
if (developerPayload == null) {
} else {
}
Log.e(TAG, "onPurchaseStateChangeCheck: " + "onPurchaseStateChange");
if (purchaseState == PurchaseState.PURCHASED) {
/** TODO: */
Toast.makeText(
mContext,
"You successfully upgraded to the entire Volume One. Enjoy!",
Toast.LENGTH_SHORT).show();
finish();
}
}
#Override
public void onRequestPurchaseResponse(RequestPurchase request,
ResponseCode responseCode) {
if (Consts.DEBUG) {
Log.d(TAG, request.mProductId + ": " + responseCode);
}
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.i(TAG, "purchase was successfully sent to server");
}
} else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
if (Consts.DEBUG) {
Log.i(TAG, "user canceled purchase");
}
} else {
if (Consts.DEBUG) {
Log.i(TAG, "purchase failed");
}
}
}
#Override
public void onRestoreTransactionsResponse(RestoreTransactions request,
ResponseCode responseCode) {
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.d(TAG, "completed RestoreTransactions request");
}
// Update the shared preferences so that we don't perform
// a RestoreTransactions again.
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(DB_INITIALIZED, true);
edit.commit();
mOwnedItemsCursor = mPurchaseDatabase
.queryAllPurchasedHistroyTabelItems();
Log.d(TAG, String.valueOf(mOwnedItemsCursor.getCount()));
startManagingCursor(mOwnedItemsCursor);
if (mOwnedItemsCursor.getCount() > 0) {
Log.d(TAG, "Updating the DB");
Toast.makeText(
mContext,
"You successfully upgraded to the entire Volume One. Enjoy!",
Toast.LENGTH_SHORT).show();
finish();
}
} else {
if (Consts.DEBUG) {
Log.d(TAG, "RestoreTransactions error: " + responseCode);
}
}
}
}
#Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_CANNOT_CONNECT_ID:
return createDialog(R.string.cannot_connect_title,
R.string.cannot_connect_message);
case DIALOG_BILLING_NOT_SUPPORTED_ID:
return createDialog(R.string.billing_not_supported_title,
R.string.billing_not_supported_message);
default:
return null;
}
}
private Dialog createDialog(int titleId, int messageId) {
String helpUrl = replaceLanguageAndRegion(getString(R.string.help_url));
if (Consts.DEBUG) {
Log.i(TAG, helpUrl);
}
final Uri helpUri = Uri.parse(helpUrl);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(titleId)
.setIcon(android.R.drawable.stat_sys_warning)
.setMessage(messageId)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(R.string.learn_more,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
Intent intent = new Intent(Intent.ACTION_VIEW,
helpUri);
startActivity(intent);
}
});
return builder.create();
}
/**
* Replaces the language and/or country of the device into the given string.
* The pattern "%lang%" will be replaced by the device's language code and
* the pattern "%region%" will be replaced with the device's country code.
*
* #param str
* the string to replace the language/country within
* #return a string containing the local language and region codes
*/
private String replaceLanguageAndRegion(String str) {
// Substitute language and or region if present in string
if (str.contains("%lang%") || str.contains("%region%")) {
Locale locale = Locale.getDefault();
str = str.replace("%lang%", locale.getLanguage().toLowerCase());
str = str.replace("%region%", locale.getCountry().toLowerCase());
}
return str;
}
private void restoreDatabase() {
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
if (!initialized) {
mBillingService.restoreTransactions();
// Toast.makeText(this, "restoring...", Toast.LENGTH_LONG).show();
}
}
}
There are 2 ways
Maintain a database on your server end, and create a table for user+purchased list of products+expiry date
Or the client app to save a encrypted code on shared preferences (so that it cant be hacked easily)
Android API's dont provide you with any inventory apis to maintain your purchases.
I used the 2nd option.
-- single product purchase
store.purchase( { "android.test.purchased" } )
-- multi-item purchase
store.purchase( { "android.test.purchased", "android.test.canceled" } )
in reference to Getting Started with Android In-app Billing
this code maybe the one you are looking for
The sample in-app billing code from Google is a good start. I have posted a link to a tutorial I gave which is three parts. This link is the first of three. I hope it helps.
http://www.mobileoped.com/2012/04/06/android-google-play-in-app-billing-part-1/

Android: change default Home Application

for some specific requirement
I am required to change Android Default Home application
with my customized Home application ( a setting inside my app that will toggle default home = my application or previous home)
I don't want the user to travel android settings that are very complicated.
Can any one help me out like where it registers launcher.apk for default
home application or how to change that
The only thing I could find was that old question: How to change default Android's Desktop application?
but no answers at all.
I have seen HomeSwitcher in the Market that do the trick, but no answer for the developer that might certainly be busy.
EDIT
I found this on the web http://www.mail-archive.com/android-developers#googlegroups.com/msg74167.html
But I got the same issue:
this is my code:
private void makePreferred() {
PackageManager pm = getPackageManager();
IntentFilter f = new IntentFilter("android.intent.action.MAIN");
f.addCategory("android.intent.category.HOME");
f.addCategory("android.intent.category.DEFAULT");
ComponentName cn = new ComponentName("com.example.android.home", "com.example.android.home.Home");
pm.addPreferredActivity(f, IntentFilter.MATCH_CATEGORY_EMPTY, null, cn);
I have the android.permission.SET_PREFERRED_APPLICATIONS set in the
manifest. After executing the code above, the logs claim things have
been added like expected (same logs as when I tick off "Make default"
from IntentResolver's list). However, when I proceed by clicking home,
the list still shows up and the logs say:
INFO/PackageManager(52): Result set changed, dropping preferred
activity for Intent { act=android.intent.action.MAIN cat=
[android.intent.category.HOME] flg=0x10200000 } type null
So it seems the resolver deletes the default entry. Am I doing
something wrong, or is this a security measure? What are the ideas
behind this?
I did an extensive research on that and starting from 2.2 there is no way to do that. The only way is using some hacking that toddler lock app does but this app put samsung phones recently in the infinite loop, so it is a risky approach.
if you look at the froyo source code here of packagemanager class, you will see this small condition in the addPreferredActivity method:
if (getUidTargetSdkVersionLockedLP(Binder.getCallingUid())
< Build.VERSION_CODES.FROYO) {
Slog.w(TAG, "Ignoring addPreferredActivity() from uid"
+ Binder.getCallingUid());
return;
}
HomeSwitcher does not work properly on 2.2 since it uses this very method and developer made a comment on app page "Froyo(2.2) is not supported
due to the API change"
"Result set changed" means that the set of packages matching that intent has changed from the set you specified when you created the default - - so the default is no longer valid. Your list of components (which you are currently setting to null) needs to contain all homescreen apps present on device, not just yours.
Here's example code that I have tested (using adb shell am start http://www.google.co.uk/ ) and used to set the default browser. XXX represents a customer name that I had to black out.
Note that in order to call addPreferredActivity you must have compiled against a minimum-sdk version of 8 (2.2) and you must have specified the SET_PREFERRED_APPLICATIONS permission. That permission is protection level 2, so you need to be signed with the same certificate as PackageManager.
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.VIEW");
filter.addCategory("android.intent.category.DEFAULT");
filter.addDataScheme("http");
Context context = getApplicationContext();
ComponentName component = new ComponentName("com.opera.mini.XXX", "com.opera.mini.XXX.CustomerBrowser");
ComponentName[] components = new ComponentName[] {new ComponentName("com.android.browser", "com.android.browser.BrowserActivity"),
component};
pm.addPreferredActivity(filter, IntentFilter.MATCH_CATEGORY_SCHEME, components, component);
ETA - if you marked this response down, could you let me know why. The code I posted above is tested and working...
startActivity(new Intent(Settings.ACTION_HOME_SETTINGS));
This code works on my ICS device: I use a service that is sensible to some call, one of them is called SET_PREFERRED_LAUNCHER, the put in a bundle your new default Launcher package (PREFERRED_PACKAGE_KEY) and it's activity (PREFERRED_ACTIVITY_KEY)
Method installPackageMethod = null;
Method deletePackageMethod = null;
Method setPreferredActivityMethod = null;
Method replacePreferredActivityMethod = null;
Object pm = null;
#Override
public void onCreate() {
super.onCreate();
if (pm == null)
pm = getPackageManager();
try {
if (setPreferredActivityMethod == null)
setPreferredActivityMethod = pm.getClass().getMethod(
"addPreferredActivity", IntentFilter.class, int.class,
ComponentName[].class, ComponentName.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
private final class ServiceHandler extends Handler {
private Context context;
public ServiceHandler(Looper looper, Context ctx) {
super(looper);
context = ctx;
}
#Override
public void handleMessage(Message msg) {
Intent intent = (Intent) msg.getData().getParcelable(
UPDATER_SERVICE_ACTION);
int request = intent.getIntExtra(
REQUEST_KEY,
REQUEST_UNKNOWN);
Bundle bundle = intent.getExtras();
switch (request) {
case INSTALL_APPLICATION: {
if (bundle != null) {
String appPath = bundle
.getString(APP_PATH_KEY);
if (appPath != null) {
LogUtil.e(TAG, "try to install " + appPath);
try {
am.installPackage(appPath);
} catch (Exception e) {
e.printStackTrace();
}
LogUtil.e(TAG, "install of " + appPath + " done");
}
}
break;
}
case UNISTALL_PACKAGE: {
if (bundle != null) {
String packagename = bundle
.getString(PACKAGE_NAME_KEY);
if (packagename != null) {
LogUtil.e(TAG, "unistall " + packagename);
try {
deletePackageMethod
.invoke(pm, packagename, null, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
break;
}
case SET_PREFERRED_LAUNCHER: {
if (bundle != null) {
String package_name = bundle
.getString(PREFERRED_PACKAGE_KEY);
if (package_name == null) {
LogUtil.e(TAG,
"WARNING: setDefaultActivity cannot continue, package is NULL");
return;
}
String activity_name = bundle
.getString(PREFERRED_ACTIVITY_KEY);
if (activity_name == null) {
LogUtil.e(TAG,
"WARNING: setDefaultActivity cannot continue, activity is NULL");
return;
}
LogUtil.e(TAG, "setDefaultActivity activity="
+ activity_name + " package=" + package_name);
IntentFilter filter = new IntentFilter(
"android.intent.action.MAIN");
filter.addCategory("android.intent.category.HOME");
filter.addCategory("android.intent.category.DEFAULT");
ComponentName[] components = new ComponentName[] {
new ComponentName("com.android.launcher",
"com.android.launcher2.Launcher"),
new ComponentName(package_name, activity_name) };
ComponentName activity = new ComponentName(package_name,
activity_name);
try {
setPreferredActivityMethod.invoke(pm, filter,
IntentFilter.MATCH_CATEGORY_EMPTY, components,
activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
Remember to add in your manifest file this permission:
<uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"/>
Usage:
public void setPreferredLauncher(String activity_name,String package_name)
{
Intent intent = new Intent(UPDATER_SERVICE_ACTION);
intent.putExtra(REQUEST_KEY, SET_PREFERRED_LAUNCHER);
intent.putExtra(PREFERRED_ACTIVITY_KEY, activity_name);
intent.putExtra(PREFERRED_PACKAGE_KEY, package_name);
context.startService(intent);
}
where:
public static final String _UPDATER_SERVICE_ACTION = "com.android.updaterservice.ACTION";
public static final String REQUEST_KEY = "com.android.updaterservice.REQUEST_KEY";
public static final String PACKAGE_NAME_KEY = "com.android.updaterservice.PACKAGE_NAME_KEY";
public static final String APP_PATH_KEY = "com.android.updaterservice.APP_PATH_KEY";
public static final String PREFERRED_ACTIVITY_KEY = "com.android.updaterservice.PREFERRED_ACTIVITY_KEY";
public static final String PREFERRED_PACKAGE_KEY = "com.android.updaterservice.PREFERRED_PACKAGE_KEY";
public static final String INSTALL_PACKAGE_RESULT = "com.android.updaterservice.INSTALL_PACKAGE_RESULT";
public static final String PACKAGE_NAME = "PACKAGE_NAME";
public static final String INSTALL_SUCCEEDED = "INSTALL_SUCCEEDED";
public static final int REQUEST_UNKNOWN = -1;
public static final int INSTALL_APPLICATION = 1;
public static final int UNISTALL_PACKAGE = 2;
public static final int SET_PREFERRED_LAUNCHER = 3;

Categories

Resources