Service Rifle Scoring License Agreement

Service Rifle Scoring License Agreement and Terms of Use

PLEASE READ THE FOLLOWING LICENSE AGREEMENT

LAST MODIFIED DATE: 08/21/2017

This License Agreement is found online at: http://harlie.com/service-rifle-scoring-license-agreement

DISCLAIMER

Read this License Agreement (“Agreement”) carefully before clicking the “I Agree” button,
or using the mobile Application Service Rifle Scoring (“Application”).

BY CLICKING THE “I AGREE” BUTTON, OR USING THE APPLICATION, YOU ARE AGREEING TO BE BOUND BY THIS LICENSE AGREEMENT AND OUR PRIVACY POLICY.

If you do not agree to the terms of this License Agreement, or our Privacy Policy, do not click on the “I Agree” button and do not install, retain or use the Application.

LICENSE

Subject to the terms and conditions of this Agreement, including payment of any/all applicable fees, Smartvariables.com grants you a revocable, non-exclusive, non-transferable, limited license to download, install and use the Application solely for your personal, non-commercial purposes strictly in accordance with the terms of this Agreement.

RESTRICTIONS

You, the (“Licensee”) use the Application entirely at your own risk.

YOU AGREE TO NEVER OPERATE OR VIEW THE APPLICATION WHILE HANDLING A GUN.

SMARTVARIABLES.COM IS NOT RESPONSIBLE FOR DEATH, OR INJURY, OR LOSS OR DAMAGE OF PROPERTY, OR EXPENSE INCURRED AS A RESULT OF VIEWING OR USING OR NOT USING THE APPLICATION.

Licensee agrees not to, and will not permit others to:

(a) license, sub-license, sell, rent, lease, loan, assign, distribute, transmit, transfer, host, outsource, disclose or otherwise commercially exploit the Application or make the Application available to any third party;

(b) prepare derivative works from, modify, copy or use the Application in any manner except as expressly permitted in this Agreement;

(c) attempt to circumvent, disable or defeat the limitations on use of the Application;

(d) alter the Application in any way, including reconfiguration of the Application.

MODIFICATIONS TO THE APPLICATION

Smartvariables.com reserves the right to modify, suspend or discontinue, temporarily or permanently, the Application or any service to which it connects, with or without notice and without liability to you.

TERM AND TERMINATION

This Agreement shall remain in effect until terminated by you or Smartvariables.com.

Smartvariables.com may, in its sole discretion, at any time and for any or no reason, suspend or terminate this Agreement with or without prior notice.
At such a time, all affected copies of the Application will be rendered inoperable.

This Agreement will terminate immediately, without prior notice from Smartvariables.com, in the event that you fail to comply with any provision of this Agreement.
You may also terminate this Agreement by deleting the Application and all copies thereof from your mobile device.

Upon termination of this Agreement, Licensee shall cease all use of the Application and delete all copies of the Application from your mobile device.

All payment obligations shall survive any termination or expiration of this Agreement.

LIMITATION OF LIABILITY

IN NO EVENT SHALL SMARTVARIABLES.COM OR ANY OF ITS PARTNERS BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF USE, BUSINESS INTERRUPTION, LOSS OF DATA, COST OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INJURY, DAMAGE, DEATH, OR SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND IN CONNECTION WITH OR ARISING OUT OF THE USE OR INABILITY TO USE THE APPLICATION, WHETHER ALLEGED AS A BREACH OF CONTRACT OR WRONGFUL CONDUCT, INCLUDING NEGLIGENCE, EVEN IF A PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SMARTVARIABLES.COM IS NOT RESPONSIBLE AND DISCLAIMS ALL LIABILITY FOR ANY DAMAGE, INJURY, DEATH, DELAY, OR FAILURE RESULTING FROM SUCH PROBLEMS. THE APPLICATION CLOUD SERVICES MAY BE SUBJECT TO LIMITATIONS, DELAYS, INACCESSIBILITY AND OTHER PROBLEMS INHERENT IN THE USE OF THE INTERNET. YOU ARE FULLY RESPONSIBLE FOR INTERNET ACCESS AND CONNECTIVITY.

WARRANTY DISCLAIMER

THE APPLICATION IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. SMARTVARIABLES.COM AND ITS PARTNERS MAKE NO WARRANTIES WHETHER EXPRESSED, IMPLIED OR STATUTORY REGARDING OR RELATING TO THE APPLICATION SOFTWARE, DOCUMENTATION, MATERIALS, OR SERVICES FURNISHED OR PROVIDED TO CUSTOMER UNDER THIS AGREEMENT. TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE LAW, SMARTVARIABLES.COM AND ITS PARTNERS SPECIFICALLY DISCLAIM ALL IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT WITH RESPECT TO THE APPLICATION, DOCUMENTATION, AND SERVICES PROVIDED BY SMARTVARIABLES.COM. ADDITIONALLY, SMARTVARIABLES.COM DOES NOT WARRANT RESULTS OF USE OR THAT THE APPLICATION SOFTWARE AND/OR CLOUD SERVICES WILL BE ERROR FREE OR THAT THE CUSTOMER’S USE OF THE APPLICATION WILL BE UNINTERRUPTED. SMARTVARIABLES.COM USES A THIRD PARTY DATA CENTER TO HOST CLOUD SERVICES. CUSTOMER ACKNOWLEDGES THAT SMARTVARIABLES.COM DOES NOT CONTROL THE TRANSFER OF DATA OVER SUCH THIRD PARTY FACILITIES, INCLUDING THE INTERNET, AND THAT THE APPLICATION’S CLOUD SERVICE MAY BE SUBJECT TO LIMITATIONS, DELAYS, AND OTHER PROBLEMS INHERENT IN THE USE OF SUCH THIRD PARTY FACILITIES. SMARTVARIABLES.COM IS NOT RESPONSIBLE FOR ANY DAMAGE, INJURY, DEATH, DELAYS, FAILURES, OR OTHER CLAIMS OF ANY KIND RESULTING FROM APPLICATION USE OR NON-USE.

DAMAGE CAP

IN NO EVENT SHALL SMARTVARIABLES.COM’S OR ITS PARTNER’S AGGREGATE, CUMULATIVE LIABILITY UNDER THIS AGREEMENT EXCEED THE AMOUNTS CUSTOMER WAS REQUIRED TO PAY SMARTVARIABLES.COM UNDER THIS AGREEMENT FOR THE APPLICATION SOFTWARE AND/OR SERVICES GIVING RISE TO SUCH LIABILITY, IN THE TWELVE (12) MONTHS IMMEDIATELY PRIOR TO THE EVENT GIVING RISE TO LIABILITY.

CUSTOMER AGREES THAT THE FOREGOING LIMITATIONS, EXCLUSIONS AND DISCLAIMERS ARE REASONABLE AND WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS IN ITS ESSENTIAL PURPOSE.

FEES

In any judicial proceeding between Licensee and Smartvariables.com arising out of or relating to this Agreement, the prevailing party shall be entitled to recover all reasonable expenses incurred as a result of the proceeding, including reasonable attorneys’ fees.

GOVERNING LAW

This Agreement shall be governed by and construed in accordance with the laws of the State of California without giving effect to any choice or conflict of law provision or rule (whether of the State of California or any other jurisdiction) that would cause the application of the laws of any jurisdiction. Any disputes between the parties to this Agreement shall be brought in the state or federal courts of California. Both parties agree to waive their right to a jury trial.

INDEMNIFICATION

Licensee shall indemnify and hold Smartvariables.com, its owners, partners, affiliates, officers, directors, employees, and agents harmless from and against any and all claims, costs, damages, losses, liabilities and expenses (including reasonable attorneys’ fees and costs) arising out of or relating to any third party claims, demands, losses, costs, or other damages, which may result from personal injury, death, or property damage related to the Application and/or use of the Application and/or the Application’s Data, provided that:

(a) Licensee is notified promptly of any product liability claims,

(b) gives Licensee sole control of the defense and settlement of the claim (provided that any settlement releases Smartvariables.com of all liability and such settlement does not affect Smartvariables.com’s business),

(c) all indemnified parties cooperate to the extent necessary in the defense of any product liability claims,

(d) Licensee has not compromised or settled such claim.

REMOTE APPLICATION CONFIGURATION

The Application uses the Google/Firebase remote config capability.
The Application uses this capability to determine data values that let the Application automatically reconfigure itself, as directed by Smartvariables.com Authorized Application Operators (“Operators”).
For example, each string for each kind of match has values for the number of shots, sighters, distance and target.
All of those values can be overridden using Firebase remote config.

Operators can remotely enable and/or disable any individual component part of the Application, including but not limited to menu item visibility, button and switch visibility, page visibility, feature visibility, advertising control, timers, weblinks, logging including remote logging and Application behavior.

This License Agreement and our Privacy Policy can both be changed by Operators using remote config. It is possible for Operators to completely disable the the Application via remote config.

You agree to our use and display of Notifications and/or Surveys and/or Advertisements in the Application, from remote config and/or Third Parties.

Smartvariables.com reserves the right to remotely reconfigure and/or enable and/or disable the Application or any part of the Application at any time for any or no reason.

THIRD-PARTY LINKS

Smartvariables.com reserves the right to sell and/or reconfigure and/or rename the Applications weblinks, including but not limited to Settings weblinks and Menu weblinks, at any time, and/or as per private agreement with our partners.

BANNED USERS

Smartvariables.com reserves the right and ability to ban users. We reserve the right and ability to completely disable the Application (or any part of the Application) at any time, should we determine inappropriate use or user misbehavior.
The determination of such misuse or behavior is entirely at the discretion of Smartvariables.com.

There are NO REFUNDS for banned users.

STANDARD REFUND POLICY

Smartvariables.com follows Google’s standard mobile application refund policy.

Banned users are not eligible for a refund and waive their rights to receive or claim any refund.

SEVERABILITY

If any provision of this Agreement is held to be unenforceable or invalid, such provision will be changed and interpreted to accomplish the objectives of such provision to the greatest extent possible under applicable law and the remaining provisions will continue in full force and effect.

AMENDMENTS TO THIS AGREEMENT

Smartvariables.com reserves the right, at its sole discretion, to modify or replace this License Agreement at any time for any reason.
If any Agreement is changed, the changes will be posted here and on our website.
Additionally, you will receive a popup from the Application requesting re-authorization of both this License Agreement and our Privacy Policy.

You are required to accept both this License Agreement and our Privacy Policy to use or to continue using the Application.
You are advised to consult this License Agreement regularly for any changes, as continued use is deemed approval of all changes.

COPYRIGHT

Service Rifle Scoring Copyright © 2017 by Smartvariables.com.
All Rights Reserved.

This product is protected by copyright and distributed under licenses restricting copying, distribution and decompilation.

CONTACT US

If you have any questions regarding this License Agreement and/or authorized Application use, or have questions about our practices, please contact us via email at info@harlie.com.

The Application uses the web site Harlie.com as the Official Site for transacting the Application’s business. The Application’s official home web address is:
http://harlie.com/service-rifle-scoring

Contact emails include:
info@harlie.com, support@harlie.com and privacy@harlie.com

Have a nice day.

Service Rifle Scoring Privacy Policy

Service Rifle Scoring Privacy Policy

PLEASE READ THE FOLLOWING PRIVACY POLICY

LAST MODIFIED DATE: 08/21/2017

This Privacy Policy is found online at: http://harlie.com/service-rifle-scoring-privacy-policy

DISCLAIMER

Read this Privacy Policy carefully before clicking the “I Agree” button,
or using the mobile Application Service Rifle Scoring (“Application”).

BY CLICKING THE “I AGREE” BUTTON, OR USING THE APPLICATION, YOU ARE AGREEING TO BE BOUND BY THIS PRIVACY POLICY AND OUR LICENSE AGREEMENT.

If you do not agree to the terms of this Privacy Policy, or our License Agreement, do not click on the “I Agree” button and do not install, retain or use the Application.

SERVICE RIFLE SCORING

This privacy policy governs your use of the software application Service Rifle Scoring (“Application”) for mobile devices. The Service Rifle Scoring app was created by, and is copyrighted and owned by Smartvariables.com. The Application is hosted on Harlie.com. Harlie.com is the designated Official Application support website.

The Application is a mobile app for tracking target shooting scores and analyzing shooting performance. The Application tracks and analyzes shots, rifle type, accuracy, timing, conditions and history to determine how performance changes. The app records position and timing for each shot, and also tracks: load data, caliber, bullet weight, powder charge, custom notes, location, sun, moon, wind and weather. Data can be browsed, analyzed and compared using over 100 criteria and contrast options. Complex analysis is possible.

THE INFORMATION WE COLLECT

Smartvariables.com collects Personal Information from the Application. The types of Personal Information collected can vary depending on use of Application features.

We collect user provided Personal Information (information that can be used to identify you as an individual) including user name, email, photo URL, location, and payment and subscription data. The Application obtains the information you provide when you login to register the Application. Login registration is mandatory to use basic features of the Application.

We collect Application Usage Information. We collect information you enter into our system when running the Application. All activities performed within the app are recorded, for example, we capture match details including shots and shot timing and other match information and any subsequent access to that data. The Application can “log” detailed usage activity to Firebase (the cloud) in real time. A local SQLite database records match data and purchases and the personally identifiable information you enter. That SQLite database is mirrored to Firebase (and also automatically backed up and restored by Google).

We collect GPS location information related to a match. The Application will attempt to determine your location city.

We collect information you provide to Smartvariables.com when you contact us for help.

We collect payment information for access to premium Application features and/or custom reporting services and/or subscription-based licensed use of the Application.

We collect all transaction related information, such as when you make purchases, or respond to any offers or surveys or notifications.

We may use cookies to enhance your experience on our website, Harlie.com. By using our website and/or the Application, you are agreeing to our use of cookies.

HOW WE USE THE INFORMATION WE COLLECT

All Collected Information helps us understand how the Application is being used. It helps us identify and resolve problems and keep the Application fresh, interesting and useful.

All Collected Information may be used for analytics, operations, advertising and marketing.

We may also use the information you provided us to contact you from time to time and provide you with important information, required notices and marketing promotions. We may use collected location information to provide you with shooting ranges or competitions in your area and to customize the advertisements, surveys, offers and notifications that we serve to you.

We may post customer reviews and video testimonials on the Application which may contain personally identifiable information. If we want to post a customer’s name along with their testimonial, we obtain the customer’s consent via email before posting the testimonial.

AUTOMATICALLY COLLECTED INFORMATION

Whenever you interact with the Application, Smartvariables.com, and any third-party advertisers and/or service providers, may use a variety of technologies that automatically or passively collect information about how the Application is accessed and used (“Usage Information”). Usage Information includes your Unique Device Identifier (as defined below), device type, operating system, application version, current time, location, browser type and pages served, and all use of Application features, for example sharing a match, or interacting with friends, or participating in contests.

UNIQUE DEVICE IDENTIFIER

Smartvariables.com collects the unique Android Advertising ID (AAID) that is assigned to your device. Our computers can identify your device by using that identifier. We may use the Device Identifier to analyze problems and trends or to provide attribution metrics to our advertisers and partners or for system administration. Additionally, Smartvariables.com may use the Device Identifier to help identify you during a support request, and to retrieve your shopping cart contents, and to gather demographic information for aggregate use.

COLLECTION OF REAL TIME LOCATION INFORMATION

When you use the Application, it may use GPS technology (or other similar technology) to determine your current location in order to identify the city you are located in. This Personal Information is collected by Smartvariables.com. Smartvariables.com may share your current location with other Application users or our partners.

If you do not want us to use your location for the purposes set forth above, you should turn off the location services for the mobile application located in your account settings or in your mobile phone settings and/or within the Application’s Settings selections.

SMARTVARIABLES.COM MAY DISCLOSE ANY USER PROVIDED AND AUTOMATICALLY COLLECTED INFORMATION

(a) as required by law, such as to comply with a subpoena, or similar legal process,

(b) when we believe in good faith that disclosure is necessary to protect our rights, protect your safety or the safety of others, investigate fraud, or respond to a government request,

(c) with our trusted services providers who work on our behalf, do not have an independent use of the information we disclose to them, and have agreed to adhere to the rules set forth in this privacy statement.

If Smartvariables.com is involved in a merger, acquisition, or sale of all or a portion of its assets, you will be notified via email and/or a prominent notice on our Web site, and/or via Application notification of any change in ownership or uses of this information, as well as any choices you may have regarding this information.

THIRD PARTIES

Smartvariables.com may share any Collected Information including, Device Identifiers and geographic data and Application Usage Information with third parties in the ways that are described in this privacy statement.

Smartvariables.com may share your Collected Information with third parties that perform functions for us (or for one of our partners) such as the Google/Firebase service that hosts and operates our Platform, helps analyze data, and process our subscriptions, transactions and payments; additionally we may share with sponsors, customer service representatives and third parties that participate in or administer our contests, promotions, and surveys or assist with marketing and brand partners, including but not limited to shooting ranges local to you.

Our partner advertisers, advertising networks and servers, and analytics companies use various technologies to collect data in order to send (or serve) relevant ads to users of the Application. These technologies may include the placement of cookies, the use of unique or non-unique non-personal identifiers, or the use of other technologies in the Application, which may be used to track user behavior, to track how the Application is being used, to link various devices you may use, and to build consumer profiles and possibly to serve you more relevant ads. This Privacy Policy does not cover the use of technologies employed by advertisers, advertising networks, advertising servers, and analytics companies outside of our properties.

If you login using Facebook, the Application may post to your Facebook wall. The Application will always ask for your permission before posting to your Facebook account.

WHAT ARE MY OPT-OUT RIGHTS?

You can stop all collection of information by the Application easily by uninstalling the Application. You may use the standard uninstall processes as may be available as part of your mobile device or via the mobile application marketplace or network.

DATA RETENTION POLICY, MANAGING YOUR INFORMATION

We will retain User Provided data for as long as you use the Application and indefinitely thereafter.
We will retain Collected Information and Usage Information online, and may at any time transfer and store it in aggregate, in whole or part.

CHILDREN

We do not use the Application to knowingly solicit data from or market to children under the age of 13. If a parent or guardian becomes aware that his or her child has provided us with information without their consent, he or she should contact us at privacy@harlie.com. We will delete such information from our files within a reasonable time.

SECURITY

Smartvariables.com is concerned about safeguarding the confidentiality of your information. We provide electronic, and procedural safeguards to protect information we process and maintain. For example, we limit access to this information to authorized employees and contractors who need to know that information in order to operate, develop or improve our Application. Cloud storage obtained from Google/Firebase is used to store all Collected Information. Application authentication services are provided by Google/Firebase. The Firebase database uses security rules to lock down unauthorized data access. Please be aware that, although we endeavor to provide reasonable security for information we process and maintain, no security system can prevent all potential security breaches.

REMOTE APPLICATION CONFIGURATION

The Application uses the Google/Firebase remote config capability.
The Application uses this capability to determine data values that let the Application automatically reconfigure itself, as directed by Smartvariables.com Authorized Application Operators (“Operators”).
For example, each string for each kind of match has values for the number of shots, sighters, distance and target.
All of those values can be overridden using Firebase remote config.

Operators can remotely enable and/or disable any individual component part of the Application, including but not limited to menu item visibility, button and switch visibility, page visibility, feature visibility, advertising control, timers, weblinks, logging including remote logging and Application behavior.

This Privacy Policy and our License Agreement can both be changed by Operators using remote config. It is possible for Operators to completely disable the the Application via remote config.

You agree to our use and display of Notifications and/or Surveys and/or Advertisements in the Application, from remote config and/or Third Parties.

Smartvariables.com reserves the right to remotely reconfigure and/or enable and/or disable the Application or any part of the Application at any time for any or no reason.

THIRD-PARTY LINKS

Smartvariables.com reserves the right to sell and/or reconfigure and/or rename the Applications weblinks, including but not limited to Settings weblinks and Menu weblinks, at any time, and/or as per private agreement with our partners.

ANDROID PERMISSIONS REQUESTED BY THE APPLICATION

ACCESS_WIFI_STATE
allows the app to view information about WiFi networking, such as whether WiFi is enabled and name of connected WiFi devices.

CHANGE_WIFI_STATE
Allows applications to change WiFi connectivity state.

INTERNET
allows the app to create network sockets and use custom network protocols.

ACCESS_NETWORK_STATE
allows the app to view information about network connections such as which networks exist and are connected.

ACCESS_COURSE_LOCATION
allows the app to get your approximate location. This location is derived by location services using network location sources such as cell towers and WiFi. These location services must be turned on and available to your device for the app to use them. Apps may use this to determine approximately where you are.

ACCESS_FINE_LOCATION
allows the app to get your precise location using the Global Positioning System (GPS) or network location sources such as cell towers and WiFi. These location services must be turned on and available to your device for the app to use them. Apps may use this to determine where you are.

WAKE_LOCK
allows the app to prevent the phone from going to sleep.

WRITE_EXTERNAL_STORAGE
allows the app to write to the SD card.

READ_EXTERNAL_STORAGE
allows the app to read the contents of your SD card.

GET_ACCOUNTS
allows the app to get the list of accounts known by the phone.

READ_PROFILE
allows the app to read personal profile information stored on your device, such as your name and contact information.

READ_CONTACTS
allows the app to read data about your contacts stored on your phone.

INTERACT_ACROSS_USERS_FULL
allows all possible interactions across users.

AMENDMENTS TO THIS AGREEMENT

Smartvariables.com reserves the right, at its sole discretion, to modify or replace this Privacy Policy at any time for any reason.
If any Agreement is changed, the changes will be posted here and on our website.
Additionally, you will receive a popup from the Application requesting re-authorization of both this Privacy Policy and our License Agreement.

You are required to accept both this Privacy Policy and our License Agreement to use or to continue using the Application.
You are advised to consult this Privacy Policy regularly for any changes, as continued use is deemed approval of all changes.

YOUR CONSENT

By using the Application, you are consenting to our processing of your information as set forth in this Privacy Policy now and as amended by us.
“Processing,” means using or touching/handling information in any way, including but not limited to
collecting, storing, deleting, using, combining and disclosing information, and using cookies on a computer and/or mobile device.
These activities will take place in the United States. If you reside outside the United States your information will be transferred, processed and stored under United States privacy standards.

COPYRIGHT

Service Rifle Scoring Copyright © 2017 by Smartvariables.com.
All Rights Reserved.

This product is protected by copyright and distributed under licenses restricting copying, distribution and decompilation.

CONTACT US

If you have any questions regarding this Privacy Policy, or have questions about our privacy practices, please contact us via email at privacy@harlie.com.

The Application uses the web site Harlie.com as the Official Site for transacting the Application’s business. The Application’s official home web address is:
http://harlie.com/service-rifle-scoring/

Contact emails include:
info@harlie.com, support@harlie.com and privacy@harlie.com

Have a nice day.

Radio Mystery Theater

The new Radio Mystery Theater Android app provides an easy, quality listening experience for the CBS Radio Mystery Theater. 1399 fifty-minute full-cast radio drama episodes are available for listening. The app is unique in presentation and designed for impaired users and commuters. The primary interface is a single button ‘Autoplay’ that controls playback.

When pressed, Autoplay begins with the oldest unheard episode and keeps playing. After one episode finishes, the next one automatically starts. Press Autoplay again to Pause. And again to Resume. Swipe right for next episode, left for previous episode. Long-press to Stop. Once playback begins, a seek ring appears. Use the button on that ring to adjust play position. Settings allow adjusting text size and font for readability. A ticker shows details about the currently playing episode. The app uses Material Design and is very slick. Paid and Trial versions. Companion Wear app.

Radio Mystery Theater app
Download the Paid apk free here

Youtube video here

Question of “Life, the Universe and Everything” is answered!

Mystery of the ages solved. There is a cosmology discussion happening on YouTube where startling speculation began after Lee Hounshell posted:

Zeno’s Paradox is solved. Our universe is digital: There is a smallest unit of time, which cannot be divided. There is also a smallest unit of distance, which cannot be divided. The paradox requires continuous time and continuous distance, which are not representative of our real universe’s physical laws.”

But is our real universe “real” after all?

Comments from the revelation evolved from quantum mechanics into how ‘everything’ (the multiverse) might result from “pure nothing.” The simple, yet bizarre logic is compelling with ideas worth contemplating. Excerpted from the full discussion:


Question of Life the Universe and Everything

Using Gradle for processing Android flavor and buildType with #IFDEF in Java code

If you are a ‘C’ or ‘C++’ programmer you are likely familiar with the #IFDEF syntax used for pre-processing source code. Unfortunately, Java has no pre-processor for managing conditional code like this. When building Android apps, a developer will typically handle dependencies on ‘flavor’ and ‘buildType’ by placing modified versions of source under specially named directories. For example, handling code differences between a ‘paid’ vs. ‘free’ app may lead to 4 versions: paidRelease, freeRelease, paidDebug and freeDebug. Code duplication can become even more severe when more than 2 flavors or more than 2 build types are used. That means app maintenance complexity increases exponentially; the developer must remember to change code similarly in all versions of a duplicated class.

Gradle build rules can be employed to allow a Java source file to contain conditional source for processing all flavor and build type combinations. This post shows you how to do that. I assume you are already familiar with using Gradle and Android Studio for app development.

First create a new file: ‘preprocessor.gradle‘ under your project’s root directory (The directory containing the ‘app’ folder). That file should contain the following code (click to expand):

    // -------------------------------------------------------------------------------------------------
    // Android seems to want to duplicate code using 'buildTypes' and 'flavors' but when minor differences exist
    // it is inefficient to maintain mostly duplicate copies of code this way.  The code below allows java source code
    // to be edited in place. Source sections are commented or uncommented based on specially crafted comments:
    //
    //#IFDEF 'configuration'
    //    java code for the specified 'configuration'
    //#ELSE
    //    java code for NOT the specified 'configuration'
    //#ENDIF
    //
    // The 'configuration' specified above can be a BUILD_TYPE or FLAVOR or BUILD_TYPE+FLAVOR or FLAVOR+BUILD_TYPE
    // For example: 'debug' or 'release' or 'paid' or 'free'
    //              or 'debugpaid' or 'debugfree 'or 'releasepaid' or 'releasefree'
    //              or 'paiddebug' or 'freedebug' or 'paidrelease' or 'freerelease'..
    //              these are all valid 'configuration' entries and will be processed by #IFDEF depending on buildType and flavor.
    // Note that nested #IFDEF statements are not supported (and there is no actual need to nest).
    // Also the 'configuration' is case independent
    //
    // To use this preprocessor, add the following line to your app/build.gradle:
    //     apply from: '../preprocessor.gradle'
    //
    // Then in your java source with build dependencies, do something like this:
    //
    //#IFDEF 'paidRelease'
    //Log.v(TAG, "example of #IFDEF 'paidRelease'");
    //#ELSE
    //Log.v(TAG, "example of NOT #IFDEF 'paidRelease'");
    //#ENDIF
    //
    // Now during a gradle build, the appropriate lines of java code will be commented and uncommented as required.
    //
    // Author: Lee Hounshell - lee.hounshell@gmail.com - Jan 11, 2016
    // See: http://harlie.com/?p=38
    
     
    String sourceDirectory = 'src'
    FileTree javaFiles = fileTree(sourceDirectory) {
        include '**/*.java'
    }
     
    // auto comment and uncomment source lines between #IFDEF 'configuration' and #ELSE or #ENDIF
    // each matching java source file is edited in-place
    class PreProcessor {
     
        public enum IfdefState {
            NONE,
            IFDEF,
            ELSE
        }
     
        public static void preProcessSourceCode (FileTree javaFiles, String buildType, String flavor) {
            buildType = buildType.toLowerCase()
            flavor = flavor.toLowerCase()
            println("---> preProcessSourceCode BUILD_TYPE="+buildType+" FLAVOR="+flavor)
            String buildTypeAndFlavor = buildType + flavor
            String flavorAndBuildType = flavor + buildType
            String ifdefRegex = '^([ ]*)(\\/\\/)#IFDEF \'(.*)\'$'
            String elseRegex = '^([ ]*)(\\/\\/)#ELSE$'
            String endifRegex = '^([ ]*)(\\/\\/)#ENDIF$'
            String lineRegex = '^([ ]*)([^ ][^ ])(.*)$'
            String singleCharLineRegex = '^([ ]*)([^ ])$'
            String comment = "//"
            String newline = System.getProperty("line.separator")
     
            javaFiles.each { File javaFile ->
                println "checking for '$ifdefRegex' in $javaFile.name"
                String content = javaFile.getText()
                StringBuilder newContent = new StringBuilder()
                IfdefState match = IfdefState.NONE
                boolean changed = false;
                String buildTypeAndOrFlavor = "<undefined>"
                content.eachLine { line, index ->
                    // process #IFDEF
                    if (line.matches(ifdefRegex)) {
                        buildTypeAndOrFlavor = (line.split('\'')[1]).toLowerCase()
                        println("--> #IFDEF on line $index for $buildTypeAndOrFlavor")
                        if (buildTypeAndOrFlavor.equals(buildType)) {
                            match = IfdefState.IFDEF
                            println("--> $buildTypeAndOrFlavor IS A MATCH FOR BUILD_TYPE $buildType")
                        }
                        else if (buildTypeAndOrFlavor.equals(flavor)) {
                            match = IfdefState.IFDEF
                            println("--> $buildTypeAndOrFlavor IS A MATCH FOR FLAVOR $flavor")
                        }
                        else if (buildTypeAndOrFlavor.equals(buildTypeAndFlavor)) {
                            match = IfdefState.IFDEF
                            println("--> $buildTypeAndOrFlavor IS A MATCH FOR COMBO BUILD_TYPE PLUS FLAVOR $buildTypeAndFlavor")
                        }
                        else if (buildTypeAndOrFlavor.equals(flavorAndBuildType)) {
                            match = IfdefState.IFDEF
                            println("--> $buildTypeAndOrFlavor IS A MATCH FOR COMBO FLAVOR PLUS BUILD_TYPE $flavorAndBuildType")
                        }
                        else {
                            match = IfdefState.ELSE
                            println("--> $buildTypeAndOrFlavor IS NOT A MATCH FOR BUILD_TYPE $buildType OR FLAVOR $flavor OR COMBO $buildTypeAndFlavor OR COMBO $flavorAndBuildType")
                        }
                    }
                    // process #ELSE
                    else if (line.matches(elseRegex)) {
                        println("--> #ELSE on line $index for $buildTypeAndOrFlavor")
                        if (match != IfdefState.ELSE) {
                            match = IfdefState.ELSE
                            println("--> $buildTypeAndOrFlavor IS NOT A MATCH FOR #ELSE")
                        }
                        else {
                            match = IfdefState.IFDEF
                            println("--> $buildTypeAndOrFlavor IS A MATCH FOR #ELSE")
                        }
                    }
                    // process #ENDIF
                    else if (line.matches(endifRegex)) {
                        println("--> #ENDIF on line $index for $buildTypeAndOrFlavor")
                        match = IfdefState.NONE
                    }
                    // comment or uncomment code or leave it unchanged
                    else {
                        if (match == IfdefState.IFDEF) { // ifdef: uncomment lines up to #ELSE or #ENDIF, as needed
                            if (line.matches(lineRegex)) {
                                def matcher = line =~ lineRegex
                                if (matcher[0][2].equals(comment)) {
                                    line = matcher[0][1] + matcher[0][3]
                                    changed = true
                                    println(line)
                                }
                            }
                        } else if (match == IfdefState.ELSE) { // else: comment-out lines to #ELSE or #ENDIF, as needed
                            if (line.matches(lineRegex)) {
                                def matcher = line =~ lineRegex
                                if (!matcher[0][2].equals(comment)) {
                                    line = matcher[0][1] + comment + matcher[0][2] + matcher[0][3]
                                    changed = true
                                    println(line)
                                }
                            }
                            else if (line.matches(singleCharLineRegex)) {
                                def matcher = line =~ singleCharLineRegex
                                if (!matcher[0][2].equals(comment)) {
                                    line = matcher[0][1] + comment + matcher[0][2]
                                    changed = true
                                    println(line)
                                }
                            }
                        }
                    }
                    newContent.append(line + newline)
                }
                // save the file if was edited
                if (changed) {
                    println("==> EDITING THE FILE <==")
                    javaFile.setText(newContent.toString())
                }
            }
        }
     
    }
     
    task preProcessSourceCodeDebugFree << {
        logger.quiet("---> PreProcessor.preProcessSourceCode(javaFiles, 'debug', 'free')")
        description("preprocess free code after //#IFDEF 'debug' to //#ENDIF")
        PreProcessor.preProcessSourceCode(javaFiles, 'debug', 'free')
    }
     
    task preProcessSourceCodeDebugPaid << {
        logger.quiet("---> PreProcessor.preProcessSourceCode(javaFiles, 'debug', 'paid')")
        description("preprocess paid code after //#IFDEF 'debug' to //#ENDIF")
        PreProcessor.preProcessSourceCode(javaFiles, 'debug', 'paid')
    }
     
    task preProcessSourceCodeReleaseFree << {
        logger.quiet("---> PreProcessor.preProcessSourceCode(javaFiles, 'release', 'free')")
        description("preprocess free code after //#IFDEF 'release' to //#ENDIF")
        PreProcessor.preProcessSourceCode(javaFiles, 'release', 'free')
    }
     
    task preProcessSourceCodeReleasePaid << {
        logger.quiet("---> PreProcessor.preProcessSourceCode(javaFiles, 'release', 'paid')")
        description("preprocess paid code after //#IFDEF 'release' to //#ENDIF")
        PreProcessor.preProcessSourceCode(javaFiles, 'release', 'paid')
    }
     
    tasks.whenTaskAdded { task ->
        if (task.name == 'compileFreeDebugJavaWithJavac') {
            logger.quiet('---> compileFreeDebugJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeDebugFree
            preProcessSourceCodeDebugFree.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compileFreeReleaseJavaWithJavac') {
            logger.quiet('---> compileFreeReleaseJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeReleaseFree
            preProcessSourceCodeReleaseFree.outputs.upToDateWhen { false } // always run
        }
        if (task.name == 'compilePaidDebugJavaWithJavac') {
            logger.quiet('---> compilePaidDebugJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeDebugPaid
            preProcessSourceCodeDebugPaid.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compilePaidReleaseJavaWithJavac') {
            logger.quiet('---> compilePaidReleaseJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeReleasePaid
            preProcessSourceCodeReleasePaid.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compileFreeDebugUnitTestJavaWithJavac') {
            logger.quiet('---> compileFreeDebugUnitTestJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeDebugFree
            preProcessSourceCodeDebugFree.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compileFreeReleaseUnitTestJavaWithJavac') {
            logger.quiet('---> compileFreeReleaseUnitTestJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeReleaseFree
            preProcessSourceCodeReleaseFree.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compilePaidDebugUnitTestJavaWithJavac') {
            logger.quiet('---> compilePaidDebugUnitTestJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeDebugPaid
            preProcessSourceCodeDebugPaid.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compilePaidReleaseUnitTestJavaWithJavac') {
            logger.quiet('---> compilePaidReleaseUnitTestJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeReleasePaid
            preProcessSourceCodeReleasePaid.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compileFreeDebugAndroidTestJavaWithJavac') {
            logger.quiet('---> compileFreeDebugAndroidTestJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeDebugFree
            preProcessSourceCodeDebugFree.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compileFreeReleaseAndroidTestJavaWithJavac') {
            logger.quiet('---> compileFreeReleaseAndroidTestJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeReleaseFree
            preProcessSourceCodeReleaseFree.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compilePaidDebugAndroidTestJavaWithJavac') {
            logger.quiet('---> compilePaidDebugAndroidTestJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeDebugPaid
            preProcessSourceCodeDebugPaid.outputs.upToDateWhen { false } // always run
        }
        else if (task.name == 'compilePaidReleaseAndroidTestJavaWithJavac') {
            logger.quiet('---> compilePaidReleaseAndroidTestJavaWithJavac dependsOn preProcessSourceCode')
            task.dependsOn preProcessSourceCodeReleasePaid
            preProcessSourceCodeReleasePaid.outputs.upToDateWhen { false } // always run
        }
    }

The code above works by allowing gradle to modify your source code, in place, when a build is run. Source code for your app will be dynamically edited to reflect the current settings for flavor and buildType. Now modify your ‘app/build.gradle‘ and add the line:

apply from: '../preprocessor.gradle'

so that your build includes the new preprocessor.gradle rules. Below is an example showing what the ‘app/build.gradle‘ file might look like if using free and paid builds. Important: Only the second line in the example shown below is needed to pull in the new build rules. This example shows a full build configuration only for completeness:

    apply plugin: 'com.android.application'
    apply from: '../preprocessor.gradle'
    
    android {
    
        ext.addDependency = {
            task, flavor, dependency ->
                println('task='+(String)task+'flavor='+(String)flavor+'dependency='+(String)dependency)
        }
    
        if (project.hasProperty("MyProject.properties")
                && new File(project.property("MyProject.properties") as String).exists()) {
    
            Properties props = new Properties()
            props.load(new FileInputStream(file(project.property("MyProject.properties"))))
    
            signingConfigs {
                release {
                    keyAlias props['keystore.alias']
                    keyPassword props['keystore.password']
                    storeFile file(props['keystore'])
                    storePassword props['keystore.password']
                }
                debug {
                    keyAlias props['keystore.alias']
                    keyPassword props['keystore.password']
                    storeFile file(props['keystore'])
                    storePassword props['keystore.password']
                }
            }
        }
    
        compileSdkVersion 'Google Inc.:Google APIs:23'
        buildToolsVersion "23.0.2"
    
        defaultConfig {
            applicationId "com.example.builditbigger"
            minSdkVersion 14
            targetSdkVersion 23
            versionCode 1
            versionName "1.0"
        }
    
        buildTypes {
            debug {
                debuggable true
            }
            release {
                minifyEnabled true
                shrinkResources true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                signingConfig signingConfigs.release
            }
        }
    
        productFlavors {
            paid {
                applicationId "com.example.builditbigger.paid"
                versionName "1.0-Paid"
            }
            free {
                applicationId "com.example.builditbigger.free"
                versionName "1.0-Free"
            }
        }
    
    }
    
    repositories {
        mavenCentral()
        jcenter()
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:23.1.1'
        compile 'com.android.support:design:23.1.1'
        // Added for AdMob
        freeCompile 'com.google.android.gms:play-services:8.4.0'
        freeCompile 'com.google.android.gms:play-services-ads:8.4.0'
    }
    

Now your app is ready to use #IFDEF #ELSE and #ENDIF when compiling Java code. Because Java does not recognize these new keywords, we need to prefix them with ‘//‘ so they are marked as comments. Here is an example ‘CheckPlayStore.java‘ class showing how that is done. In this example, the ImageView upgrade_paid will be null for release builds and non-null for free builds (assuming of course that your free layout.xml contains an ImageView with id ‘upgrade_to_paid‘).  This means the block of code after

if (upgrade_paid != null)

will only execute for free builds. The included Logging examples also show how to conditionally check for both flavor and buildType together. Note that the #IFDEF ‘configuration’ directive is case-independent:

    package com.example.builditbigger.util;
    
    import android.app.Activity;
    import android.content.ActivityNotFoundException;
    import android.content.Context;
    import android.content.Intent;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;
    import android.net.Uri;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    
    import com.example.builditbigger.R;
    
    public class CheckPlayStore {
        private final static String TAG = "EXAMPLE: <" + CheckPlayStore.class.getSimpleName() + ">";
    
        public static void upgradeToPaid(final Activity activity) {
            @SuppressWarnings("UnusedAssignment") ImageView upgrade_paid = null;
    
            //---------------------------------------------------------------------------------------------------------
            // IMPORTANT NOTE: the following #IFDEF #ELSE and #ENDIF directives are processed in build.gradle prior to javac
            //                 CODE IN THIS BLOCK DEPENDS ON 'BUILD_TYPE' AND/OR 'FLAVOR' AND IS DYNAMICALLY EDITED BY GRADLE
            //---------------------------------------------------------------------------------------------------------
            //#IFDEF 'free'
            //upgrade_paid = (ImageView) activity.findViewById(R.id.upgrade_to_paid);
            //#ENDIF
    
            // combo BUILD_TYPE+FLAVOR and FLAVOR+BUILD_TYPE examples..
    
            //#IFDEF 'freeDebug'
            //Log.v(TAG, "example of #IFDEF 'freeDebug'");
            //#ELSE
            //Log.v(TAG, "example of NOT #IFDEF 'freeDebug'");
            //#ENDIF
    
            //#IFDEF 'releaseFree'
            //Log.v(TAG, "example of #IFDEF 'releaseFree'");
            //#ELSE
            //Log.v(TAG, "example of NOT #IFDEF 'releaseFree'");
            //#ENDIF
    
            //#IFDEF 'DEBUGPAID'
            //Log.v(TAG, "example of #IFDEF 'DEBUGPAID'");
            //#ELSE
            //Log.v(TAG, "example of NOT #IFDEF 'DEBUGPAID'");
            //#ENDIF
    
            //#IFDEF 'paidRelease'
            //Log.v(TAG, "example of #IFDEF 'paidRelease'");
            //#ELSE
            //Log.v(TAG, "example of NOT #IFDEF 'paidRelease'");
            //#ENDIF
            //---------------------------------------------------------------------------------------------------------
    
            //noinspection ConstantConditions
            if (upgrade_paid != null) {
                Log.v(TAG, "using FREE version.");
                upgrade_paid.setOnClickListener(new View.OnClickListener() {
                    public void onClick(View v) {
                        String packageId = activity.getApplicationContext().getPackageName();
                        packageId = packageId.replace(".free", ".paid");
                        Log.v(TAG, "packageId="+packageId);
                        try {
                            // from: http://stackoverflow.com/questions/3239478/how-to-link-to-android-market-app
                            String upgradeLink = "http://market.android.com/details?id=" + packageId;
                            if (CheckPlayStore.isGooglePlayInstalled(activity.getApplicationContext())) {
                                upgradeLink = "market://details?id=" + packageId;
                            }
                            Log.v(TAG, "upgradeLink=" + upgradeLink);
                            activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(upgradeLink)));
                        }
                        catch (ActivityNotFoundException e) {
                            Log.e(TAG, "APP id='"+packageId+"' NOT FOUND ON PLAYSTORE!");
                        }
                    }
                });
            }
            else {
                Log.v(TAG, "using PAID version.");
            }
        }
    
        // from: http://stackoverflow.com/questions/15401748/how-to-detect-if-google-play-is-installed-not-market
        private static boolean isGooglePlayInstalled(Context context) {
            PackageManager pm = context.getPackageManager();
            boolean app_installed;
            try
            {
                PackageInfo info = pm.getPackageInfo("com.android.vending", PackageManager.GET_ACTIVITIES);
                String label = (String) info.applicationInfo.loadLabel(pm);
                app_installed = (label != null && ! label.equals("Market"));
            }
            catch (PackageManager.NameNotFoundException e)
            {
                app_installed = false;
            }
            Log.v(TAG, "isGooglePlayInstalled=" + app_installed);
            return app_installed;
        }
    
    }

enjoy.

Your support for my work is greatly appreciated!